Thank you for your interest in contributing to the LiveTemplate core library! This guide covers contributions to the Go server-side library only.
LiveTemplate is distributed across multiple repositories. Please use the appropriate contribution guide:
This guide will help you get started with core library contributions.
Before you begin, ensure you have the following installed:
# macOS
brew install golangci-lint
# Linux/WSL
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
Note: For client library development (TypeScript), see the client repository.
Fork and clone the repository
git clone https://github.com/yourusername/livetemplate.git
cd livetemplate
Install dependencies
# Go dependencies (automatically handled by Go modules)
go mod download
Install pre-commit hook (automatically validates before each commit)
cp scripts/pre-commit.sh .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
Verify setup
# Run all tests
go test -v ./... -timeout=30s
# Run linter
golangci-lint run
Create a feature branch
git checkout -b feature/your-feature-name
# or
git checkout -b fix/your-bug-fix
Make your changes
Run tests frequently
# Quick feedback loop
go test -v ./...
# Or test specific packages
go test -v -run TestYourSpecificTest
Commit your changes
git add .
git commit -m "your commit message"
# Pre-commit hook will automatically run validation
When making changes to the core library, you may want to test how they affect LVT or examples before releasing.
The easiest way is to use Go workspaces (Go 1.18+). This automatically uses local checkouts without modifying any go.mod files.
Directory structure:
parent/
├── livetemplate/ (core library - this repo)
├── lvt/ (CLI tool)
├── examples/ (example apps)
├── client/ (TypeScript client - optional)
└── setup-workspace.sh (run this once)
One-time setup:
# Clone sibling repositories
cd .. # Go to parent directory
git clone https://github.com/livetemplate/lvt.git
git clone https://github.com/livetemplate/examples.git
# Create workspace (run once)
./setup-workspace.sh
That's it! Now all go commands automatically use your local versions:
# Test LVT with your core changes
cd lvt
go test ./... # Automatically uses ../livetemplate
# Test examples
cd ../examples
./test-all.sh # Automatically uses ../livetemplate and ../lvt
# Build an example
cd counter
go build # Uses local livetemplate
To remove workspace:
cd /path/to/parent
./setup-workspace.sh --clean
How it works:
go.work file in the parent directorygo.mod changes neededgo.work is gitignored (never committed)If you prefer manual control or can't use workspaces, use the helper scripts in each repo:
cd ../lvt
./scripts/setup-local-dev.sh
cd ../examples
./scripts/setup-local-dev.sh
Revert with --undo flag when done.
The core library has automated CI checks that test LVT and examples against your PR. These checks will catch breaking changes before merge.
livetemplate/
├── template.go # Main API — Template type and orchestrator
├── mount.go # Controller+State pattern, HTTP/WebSocket handlers
├── context.go # Unified Context type for action handlers
├── state.go # State interface and AsState wrapper
├── action.go # Action data binding (ActionData, FieldError, MultiError)
├── dispatch.go # Reflection-based action method dispatch
├── lifecycle.go # Controller lifecycle method detection
├── config.go # Template and handler configuration
├── auth.go # Authenticator interface and implementations
├── session_stores.go # MemorySessionStore and RedisSessionStore
├── health.go # Kubernetes health check endpoints
├── formvalidation.go # Form schema extraction and validation
├── ws.go # WebSocket interface abstraction
├── ws_gorilla.go # Gorilla WebSocket implementation
├── upload.go # File upload public API
├── testing.go # AssertPureState test helper
├── internal/ # Internal packages (5-phase architecture)
│ ├── parse/ # Phase 1: Template parsing (AST evaluation)
│ ├── build/ # Phase 2: Tree types, fingerprinting, wrapper injection
│ ├── diff/ # Phase 3: Tree comparison and update generation
│ ├── render/ # Phase 4: HTML rendering and minification
│ ├── send/ # Phase 5: Message parsing and serialization
│ ├── session/ # WebSocket connection registry
│ ├── observe/ # Metrics and Prometheus export
│ ├── keys/ # Range item key generation
│ ├── upload/ # Upload infrastructure
│ └── fuzz/ # Fuzz testing framework
├── pubsub/ # Redis pub/sub for distributed broadcasting
├── testdata/ # Test fixtures, golden files, fuzz corpus
├── docs/ # Documentation
└── scripts/ # Development scripts
For the complete file-by-file map with line counts and dependencies, see docs/design/CODE_STRUCTURE.md.
Note: The client library, CLI tool, and examples are now in separate repositories:
The pre-commit hook is CRITICAL for maintaining code quality. It automatically:
go fmtNEVER skip the pre-commit hook using --no-verify
Fix failures before committing
Formatted files are auto-added
go fmt and stages formatted files automatically🔄 Running pre-commit validation...
📝 Auto-formatting Go code...
✅ Code formatting completed
🔍 Running golangci-lint...
✅ Linting passed
🧪 Running Go tests...
✅ All Go tests passed
✅ Pre-commit validation completed successfully
Unit Tests - Fast tests for individual functions
go test -v ./... -short
E2E Tests - End-to-end tests with template rendering
go test -run TestTemplate_E2E -v
Browser Tests - Chromedp tests for real browser interactions
go test -run TestE2E -v
Fuzz Tests - Randomized input testing
go test -fuzz=FuzzTree -fuzztime=30s
Note: For client library tests, see the client repository.
Many E2E tests use golden files in testdata/e2e/:
*.html - Expected rendered HTML output*.json - Expected tree updatesTo update golden files after intentional changes:
UPDATE_GOLDEN=1 go test -run TestTemplate_E2E -v
Follow these patterns:
Unit test example:
func TestNewFeature(t *testing.T) {
t.Run("description of test case", func(t *testing.T) {
// Arrange
input := "test input"
// Act
result := YourFunction(input)
// Assert
if result != expected {
t.Errorf("expected %v, got %v", expected, result)
}
})
}
E2E browser test example:
func TestFeature(t *testing.T) {
// Setup server and browser context
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
// Run test actions
err := chromedp.Run(ctx,
chromedp.Navigate("http://localhost:8080"),
chromedp.Click("button#submit"),
chromedp.WaitVisible("#result"),
)
if err != nil {
t.Fatal(err)
}
}
Template, Context, State, Session, AsStatetreeNode, keyGenerator, parseActionTestFeatureNameBenchmarkFeatureNameThe public API surface is minimal by design. Only export:
Keep implementation details private.
// Template represents a parsed template that can generate updates.
// It maintains state between renders to produce minimal diffs.
type Template struct {
// ...
}
// ExecuteToUpdate renders the template and returns a JSON update.
// This is more efficient than ExecuteToHTML for subsequent renders.
func (t *Template) ExecuteToUpdate(data interface{}) (*UpdateResponse, error) {
// ...
}
Use conventional commit format:
<type>(<scope>): <subject>
<body>
<footer>
feat: New featurefix: Bug fixrefactor: Code restructuring without behavior changetest: Adding or updating testsdocs: Documentation changesperf: Performance improvementschore: Build process or tooling changesfeat(template): add support for nested template invokes
Implements recursive template invocation to support complex
component hierarchies. Updates tree parser to handle nested
{{template}} calls correctly.
Closes #123
fix(client): prevent duplicate WebSocket connections
Adds connection state tracking to prevent race condition where
multiple connections could be established during reconnection.
Fixes #456
refactor: minimize public API surface
BREAKING CHANGE: Internal types like TreeNode and KeyGenerator
are now private. Users should only interact with Template,
Store, and ActionContext interfaces.
.git/hooks/pre-commit
## Description
Brief description of changes
## Motivation
Why is this change needed?
## Changes
- List of specific changes
- One per line
## Testing
How was this tested?
## Checklist
- [ ] Tests added/updated
- [ ] Documentation updated
- [ ] Pre-commit hook passes
- [ ] No breaking changes (or documented if necessary)
Look for issues labeled good first issue - these are:
Core template engine (template.go, tree.go, internal/)
Documentation (docs/)
Testing (various *_test.go files)
HTTP/WebSocket handling (mount.go, session.go, context.go)
For other components:
Start with the Contributor Walkthrough
docs/guides/new-contributor-walkthrough.md - START HERE! Comprehensive guide to the 5-phase architecture with links to all code and testsRead the architecture docs
CLAUDE.md - Development guidelinesdocs/design/ARCHITECTURE.md - System architecture and design decisionsRun the examples
# Clone the examples repository
git clone https://github.com/livetemplate/examples.git
cd examples/counter
go run main.go
# Open http://localhost:8080
Read the tests
e2e_test.go for high-level flowtemplate_test.go for core functionalityExperiment
By contributing, you agree that your contributions will be licensed under the same license as the project (check LICENSE file).
Thank you for contributing to LiveTemplate!