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:
- Core Library (this repo) - Go server-side library
- Client Library - TypeScript client for browsers → Client CONTRIBUTING.md
- CLI Tool (lvt) - Code generator and dev server → LVT CONTRIBUTING.md
- Examples - Example applications → Examples CONTRIBUTING.md
This guide will help you get started with core library contributions.
- Prerequisites
- Setup
- Development Workflow
- Pre-commit Hook
- Testing
- Code Style
- Commit Messages
- Pull Requests
- Where to Start
- Getting Help
Before you begin, ensure you have the following installed:
- Go 1.26+ - Required for building and testing the core library
- golangci-lint - Required for linting (pre-commit hook)
# 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
- Chrome/Chromium - Required for E2E browser tests (chromedp)
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
- Follow existing code patterns and conventions
- Add tests for new functionality
- Update documentation if needed
-
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.shThat'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 livetemplateTo remove workspace:
cd /path/to/parent
./setup-workspace.sh --cleanHow it works:
- Creates a
go.workfile in the parent directory - Go automatically finds it and uses listed modules
- No
go.modchanges needed go.workis 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.shRevert 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:
- Client: https://github.com/livetemplate/client
- CLI (lvt): https://github.com/livetemplate/lvt
- Examples: https://github.com/livetemplate/examples
The pre-commit hook is CRITICAL for maintaining code quality. It automatically:
- Auto-formats Go code using
go fmt - Runs golangci-lint to catch common issues
- Runs all Go tests with timeout
-
NEVER skip the pre-commit hook using
--no-verify- The hook is there to catch issues early
- Skipping it will break CI and block your PR
-
Fix failures before committing
- Linting errors: Fix the code issues
- Test failures: Ensure all tests pass
- If stuck, ask for help (see Getting Help)
-
Formatted files are auto-added
- The hook runs
go fmtand stages formatted files automatically - No need to manually format before committing
- The hook runs
🔄 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 updates
To update golden files after intentional changes:
UPDATE_GOLDEN=1 go test -run TestTemplate_E2E -vFollow 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)
}
}- No unnecessary comments - Code should be self-documenting
- Follow existing patterns - Check neighboring code for conventions
- Use existing utilities - Don't reinvent the wheel
- Maintain idiomatic Go - Follow Go best practices
- Public API (exported): PascalCase
Template,Context,State,Session,AsState
- Internal implementation (unexported): camelCase
treeNode,keyGenerator,parseAction
- Test functions:
TestFeatureName - Benchmark functions:
BenchmarkFeatureName
The public API surface is minimal by design. Only export:
- Types that users directly interact with
- Functions that users must call
- Interfaces that users implement
Keep implementation details private.
- Add godoc comments for all public types and functions
- Document non-obvious behavior and edge cases
- Include examples in godoc when helpful
// 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 changes
feat(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.
- Ensure all tests pass locally
- Update documentation if needed
- Add tests for new features
- Rebase on latest main branch
- Run the pre-commit hook manually if needed:
.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)- PRs require at least one approval
- CI must pass (tests, linting, formatting)
- Address reviewer feedback
- Maintainer will merge when ready
Look for issues labeled good first issue - these are:
- Well-defined and scoped
- Don't require deep system knowledge
- Good for getting familiar with the codebase
-
Core template engine (
template.go,tree.go,internal/)- Template parsing improvements
- Tree diffing optimizations
- New Go template constructs
-
Documentation (
docs/)- Improve existing docs
- Add examples
- Fix typos or unclear sections
-
Testing (various
*_test.gofiles)- Add test coverage
- Improve E2E tests
- Add edge case tests
-
HTTP/WebSocket handling (
mount.go,session.go,context.go)- Controller+State pattern implementation
- Session management improvements
- Performance optimizations
For other components:
- Client library: livetemplate/client
- CLI tool: livetemplate/lvt
- Examples: livetemplate/examples
-
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 tests
-
Read the architecture docs
CLAUDE.md- Development guidelinesdocs/design/ARCHITECTURE.md- System architecture and design decisions
-
Run 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
- Tests are excellent documentation
- Start with
e2e_test.gofor high-level flow - Check
template_test.gofor core functionality
-
Experiment
- Make small changes
- Run tests to see what breaks
- Use debugger to step through code
- Questions: Open a discussion on GitHub
- Bugs: Open an issue with reproduction steps
- Features: Open an issue to discuss before implementing
- Real-time help: Check if there's a Discord/Slack (if available)
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!