Contributing to Neru: build instructions, architecture overview, and contribution guidelines.
- Quick Start
- Development Setup
- Building & Running
- Testing
- Testing Tiers
- Architecture Overview
- Contributing
- Release Process
- Development Tips
- Troubleshooting
- Resources
Get Neru running locally in 5 minutes:
# 1. Clone and setup
git clone https://github.com/y3owk1n/neru.git
cd neru
# 2. Set up development environment
## Option A: Using Devbox (Recommended)
devbox shell
See [Development Environment Options](#development-environment-options) for install details
## Option B: Manual installation
brew install just golangci-lint
See [Development Environment Options](#development-environment-options) for install details
# 3. Build and run
just build
./bin/neru launch
# 4. Test it works
neru hints # Should show hint overlaysNeed help? See Installation Guide for detailed setup instructions.
-
Go 1.26+ - Install Go
-
Xcode Command Line Tools -
xcode-select --install -
Just - Command runner (Install)
brew install just
-
golangci-lint - Linter (Install)
brew install golangci-lint
For the best development experience, choose one of the following setup methods:
Devbox provides an isolated development environment with all required tools pre-configured.
# Install Devbox
curl -fsSL https://get.jetify.com/devbox | bash
# Option 1: Enter the development shell manually
devbox shell
# Option 2: Use direnv for automatic shell activation (recommended)
# Install direnv: brew install direnv
# Add to your shell: eval "$(direnv hook bash)" (or zsh/fish)
# The .envrc file will automatically activate devbox when you cd into the projectDevbox automatically installs and manages:
- Go 1.26+
- gopls (Go language server)
- gotools, gofumpt, golines (Go formatting tools)
- golangci-lint (linter)
- just (command runner)
- clang-tools (C/C++ tools for CGo)
Install essential tools manually using Homebrew. Note that Devbox provides additional development tools (gopls, gofumpt, golines, etc.) that can be installed separately if desired.
brew install go just golangci-lint llvmTool descriptions:
go- Go compiler and toolchain (1.26+ required)just- Command runner for build scriptsgolangci-lint- Go linter and formatterllvm- LLVM tools including clang-format for C/C++/Objective-C formatting (required for CGo code)
Optional additional tools (install via go install if desired):
gopls:go install golang.org/x/tools/gopls@latestgofumpt:go install mvdan.cc/gofumpt@latestgolines:go install github.com/segmentio/golines@latest
git clone https://github.com/y3owk1n/neru.git
cd neru# Check Go version
go version # Should be 1.26+
# Check tools
just --version
golangci-lint --version
# List available commands
just --listFor the best development experience, we recommend:
- IDE Setup: Use VS Code with Go extension or GoLand
- EditorConfig: Install EditorConfig plugin for consistent formatting
- Pre-commit Hooks: Set up git hooks to automate formatting and linting
# Install pre-commit hooks
cp scripts/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit| Task | Command | Description |
|---|---|---|
| Build | just build |
Compile the application |
| Build | just build-darwin |
Build a macOS binary on macOS |
| Build | just build-linux |
Build a Linux foundations binary |
| Build | just build-windows |
Build a Windows foundations binary |
| Test | just test |
Run unit and integration tests |
| Test | just test-foundation |
Run cross-platform-safe core tests |
| Test | just test-unit |
Run unit tests |
| Test | just test-integration |
Run integration tests |
| Test | just test-race |
Run all tests with race detection |
| Test | just test-all |
Run all tests (unit + integration) |
| Lint | just lint |
Run linters |
| Format | just fmt |
Format code |
| Run | just run |
Build and run the application |
| Clean | just clean |
Remove build artifacts |
To debug Neru during development:
-
Enable Debug Logging:
[logging] log_level = "debug"
-
View Logs:
# macOS tail -f ~/Library/Logs/neru/app.log # Linux tail -f ~/.local/state/neru/log/app.log
-
Use Delve Debugger:
dlv debug ./cmd/neru
Neru uses Just as a command runner (alternative to Make).
# Development build
just build
# Run
./bin/neru launch# Development build (auto-detects version from git tags)
just build
# Build target-specific contributor binaries
just build-darwin
just build-linux
just build-windows
# Release build (optimized, stripped)
just release
# Build app bundle (macOS .app)
just bundle
# Build with custom version
just build-version v1.0.0
# Clean build artifacts
just cleanIf you are starting Linux or Windows work, this is the recommended minimum smoke-test sequence:
just build
just test-foundationThen, depending on what you are targeting:
- Linux:
just build-linux - Windows:
just build-windows - macOS:
just build-darwin
Neru uses a few different testing layers:
- Pure unit tests: Shared Go logic with no native OS dependency.
- Contract tests:
Ports and adapters should agree on error semantics such as
CodeNotSupported. - Integration tests:
Real OS/native behavior behind
integrationbuild tags. - Architecture tests: Guardrails that protect package boundaries and platform isolation.
When you add a stubbed platform feature, add or update a contract test so the unsupported behavior is explicit and stable until the real implementation lands.
For cross-platform work, prefer shared terms over macOS-specific names:
Primarymeans the default accelerator modifier:Cmdon macOS,Ctrlon Linux/Windows.- Linux is not a single backend. Treat X11 and Wayland as separate infra concerns behind the same port.
- Start backend decisions from
internal/core/infra/platform/profile.goso contributors extend one source of truth. - Do not assume CGO based on OS alone. Check the backend plan first: some Linux and most early Windows integrations can stay pure Go, while macOS and some future Linux backends require CGO.
For practical file-by-file contributor guidance, see CROSS_PLATFORM.md.
Without Just:
# Basic build
go build -o bin/neru ./cmd/neru
# With version info
VERSION=$(git describe --tags --always --dirty)
go build \
-ldflags="-s -w -X github.com/y3owk1n/neru/internal/cli.Version=$VERSION" \
-o bin/neru \
./cmd/neru
# For release (optimized)
go build \
-ldflags="-s -w -X github.com/y3owk1n/neru/internal/cli.Version=$VERSION" \
-trimpath \
-o bin/neru \
./cmd/neru-ldflags="-s -w"- Strip debug info and symbol table (smaller binary)-trimpath- Remove file system paths from binary-X pkg.Var=value- Set string variable at build time (version injection)
Neru has a comprehensive test suite with clear separation between unit tests and integration tests. For detailed testing guidelines and standards, see CODING_STANDARDS.md.
| Test Type | File Pattern | Purpose | Command | Coverage |
|---|---|---|---|---|
| Unit Tests | *_test.go |
Business logic with mocks (no build tag required) | just test |
50+ tests covering algorithms, isolated components |
| Integration Tests | *_integration_*_test.go |
Real system interactions (tagged //go:build integration && <os>) |
just test-integration |
Tests covering platform APIs, IPC, file operations |
package_test.go # Unit tests (logic, mocks)
package_integration_darwin_test.go # macOS integration tests //go:build integration && darwin
package_integration_linux_test.go # Linux integration tests //go:build integration && linux
# Unit tests only (fast, CI)
just test
# Integration tests only (comprehensive, local)
just test-integration
# All tests (unit + integration)
just test-all
# With race detection
just test-race- Domain Logic: Hint generation, grid calculations, element filtering
- Service Logic: Action processing, mode transitions, configuration validation
- Adapter Interfaces: Port implementations with mocked dependencies
- Configuration: TOML parsing, validation, defaults
- CLI Logic: Command parsing, argument validation
- Pure Logic Benchmarks: Performance testing of algorithms without system calls
- macOS Accessibility API: Real UI element access, cursor control, mouse actions
- macOS Event Tap API: Real global keyboard event interception
- macOS Hotkey API: Real global hotkey registration/unregistration
- Unix Socket IPC: Real inter-process communication
- macOS Overlay API: Real window/overlay management
- File System Operations: Real config file loading/reloading
- Component Coordination: Real service-to-adapter interactions
# Run all linters
just lint
# Auto-fix issues
golangci-lint run --fix# Watch mode (requires entr or similar)
find . -name "*.go" | entr -r just test
# Quick iteration with unit tests
just build && just test
# Full validation before commit
just test && just lint && just build
# Test specific package
go test ./internal/core/domain/hint/
# Test with verbose output
go test -v ./internal/app/services/
# Integration test specific component
go test -tags=integration ./internal/core/infra/accessibility/Neru is a keyboard-driven navigation tool that enhances productivity by allowing users to quickly navigate and interact with UI elements using keyboard shortcuts. macOS is the primary supported platform; Linux and Windows support is in progress (see ARCHITECTURE.md).
Four Navigation Modes:
- Hints Mode: Uses macOS Accessibility APIs to identify clickable elements and overlay hint labels
- Grid Mode: Divides the screen into a grid system for coordinate-based navigation
- Scroll Mode: Provides Vim-style scrolling at the cursor position
- Recursive Grid Mode: Recursive cell navigation with center preview and reset/backtrack support
All modes support various actions and can be configured extensively through TOML configuration files.
Neru uses a standardized Mode interface to ensure consistent behavior across all navigation modes. This interface defines the contract that all mode implementations must follow.
type Mode interface {
// Activate initializes and starts the mode with optional action parameters
Activate(action *string)
// HandleKey processes keyboard input during normal mode operation
HandleKey(key string)
// HandleActionKey processes keyboard input when in action sub-mode
HandleActionKey(key string)
// Exit performs cleanup and deactivates the mode
Exit()
// ToggleActionMode switches between normal mode and action sub-mode
ToggleActionMode()
// ModeType returns the domain mode type identifier
ModeType() domain.Mode
}All mode implementations follow this pattern:
- Struct Definition: Create a struct that holds a reference to the Handler
- Constructor: Provide a
NewXXXMode(handler *Handler)constructor - Interface Methods: Implement all required interface methods
- Registration: Register the mode in
Handler.NewHandler()
- Purpose: Initialize the mode and set it as the active mode
- Parameters: Optional action string for pending actions
- Responsibilities:
- Call
handler.setModeLocked()to change app state (caller holdsh.mu) - Show mode-specific overlays/UI
- Initialize mode-specific state
- Log mode activation
- Call
- Purpose: Process keyboard input during normal mode operation
- Parameters: Single key string (e.g., "a", "j", "escape")
- Responsibilities:
- Route keys to appropriate handlers
- Update mode state based on input
- Handle mode-specific navigation logic
- Purpose: Process keyboard input when in action sub-mode
- Parameters: Single key string representing action selection
- Responsibilities:
- Delegate to
handler.handleActionKey()for action execution - Handle action-specific key mappings
- Delegate to
- Purpose: Clean up mode state and return to idle
- Responsibilities:
- Hide overlays and UI elements
- Reset mode-specific state
- Perform mode-specific cleanup only (common cleanup is handled by
exitModeLocked)
- Purpose: Switch between normal mode and action sub-mode
- Responsibilities:
- Delegate to handler's toggle method (e.g.,
toggleActionModeForHints()) - Handle mode-specific action mode transitions
- Delegate to handler's toggle method (e.g.,
- Purpose: Return the domain mode identifier
- Returns:
domain.Modeenum value (e.g.,domain.ModeHints)
type ExampleMode struct {
handler *Handler
}
func NewExampleMode(handler *Handler) *ExampleMode {
return &ExampleMode{handler: handler}
}
func (m *ExampleMode) ModeType() domain.Mode {
return domain.ModeExample
}
func (m *ExampleMode) Activate(action *string) {
// NOTE: Activate is called with h.mu already held (via ActivateModeWithAction).
// Use the *Locked helpers — never the public SetMode* methods (deadlock).
m.handler.setModeLocked(domain.ModeExample, overlay.ModeExample)
// Show example overlay
// Initialize state
m.handler.logger.Info("Example mode activated")
}
func (m *ExampleMode) HandleKey(key string) {
// NOTE: HandleKey is called with h.mu already held (via HandleKeyPress).
// Use exitModeLocked — never the public SetModeIdle (deadlock).
switch key {
case "escape":
m.handler.exitModeLocked()
// Handle other keys...
}
}
func (m *ExampleMode) HandleActionKey(key string) {
m.handler.handleActionKey(key, "Example")
}
func (m *ExampleMode) Exit() {
// Hide overlays
// Reset state
}
func (m *ExampleMode) ToggleActionMode() {
m.handler.toggleActionModeForExample()
}func NewHandler(...) *Handler {
handler := &Handler{...}
handler.modes = map[domain.Mode]Mode{
domain.ModeHints: NewHintsMode(handler),
domain.ModeGrid: NewGridMode(handler),
domain.ModeAction: NewActionMode(handler),
domain.ModeScroll: NewScrollMode(handler),
// Add new modes here
}
return handler
}- Consistent Naming: Use
XXXModefor struct names,NewXXXModefor constructors - Handler Reference: Always store a reference to the Handler for accessing services
- State Management: Use the Handler's state management methods
- Logging: Log mode transitions and important events
- Error Handling: Handle errors gracefully, don't panic
- Resource Cleanup: Always clean up overlays and state in
Exit() - Action Mode Support: Implement
ToggleActionMode()even if not used
To add a new navigation mode:
- Define Domain Mode: Add to
internal/core/domain/modes.go - Create Implementation: Implement the Mode interface
- Add CLI Command: Create CLI command in
internal/cli/ - Update IPC: Add handler in
internal/app/ipc_controller.go - Register Mode: Add to Handler's mode map
- Add Tests: Create unit and integration tests
- Update Config: Add hotkey defaults
- Update Docs: Document in CLI.md and DEVELOPMENT.md
- Go - Core application logic, CLI, configuration
- CGo + Objective-C - macOS Accessibility API integration
- Cobra - CLI framework
- TOML - Configuration format
- Unix Sockets - IPC communication
Neru follows clean architecture with clear separation of concerns:
Pure business logic with no external dependencies:
- Entities: Core concepts (Hint, Grid, Element, Action)
- Value Objects: Immutable data structures
- Business Rules: Domain logic and validation
Interfaces defining contracts between layers:
- AccessibilityPort: UI element access and interaction
- OverlayPort: UI overlay management
- ConfigPort: Configuration management
- InfrastructurePort: System-level operations
Implements use cases and orchestrates domain entities:
- Services: Business logic orchestration (HintService, GridService, ActionService)
- Components: UI components for Hints, Grid, and Scroll modes
- Modes: Navigation mode implementations following the
Modeinterface - Lifecycle: Application startup, shutdown, and orchestration
Concrete implementations of ports:
- Accessibility: Platform accessibility API integration (AXUIElement on macOS)
- Overlay: UI overlay management and rendering
- Config: Configuration loading and parsing
- EventTap: Global input monitoring
- Hotkeys: System hotkey registration
- IPC: Inter-process communication
- Platform: OS-specific adapters (
platform/darwin,platform/linux,platform/windows)
User interface rendering:
- UI: Overlay rendering and coordinate conversion
- Startup: Configuration is loaded → Dependencies are wired → Hotkeys registered → App waits for input
- User Interaction: Hotkey pressed → Event tap captures → Mode activated → UI overlays displayed
- Processing: User input processed → Actions determined → System APIs called → Results rendered
- Cleanup: Mode exited → Overlays hidden → State reset → App returns to idle
Core business logic and entities (pure Go, no external dependencies):
- Element: UI element representation with bounds, role, and state
- Hint/Grid/Action: Navigation and interaction primitives
Interface contracts between layers:
- AccessibilityPort: UI element access and interaction
- OverlayPort: UI overlay management
- ConfigPort: Configuration management
- InfrastructurePort: System-level operations
Application orchestration and use cases:
- Services: Business logic orchestration (HintService, GridService, ActionService)
- Components: UI components for Hints, Grid, and Scroll modes
- Modes: Navigation mode implementations following the
Modeinterface - App: Central application state and dependencies
- Lifecycle: Startup, shutdown, and orchestration
Infrastructure implementations:
- Accessibility: Platform accessibility API integration (AXUIElement on macOS)
- Overlay: UI overlay management and rendering
- Config: Configuration loading and parsing
- EventTap: Global input monitoring
- Hotkeys: System hotkey registration
- IPC: Inter-process communication
- Platform: OS-specific adapters (
platform/darwin,platform/linux,platform/windows)
Presentation layer:
- UI: Overlay rendering and coordinate conversion
Command-line interface (Cobra-based):
- Command parsing and dispatch
- Output formatting and error handling
Configuration management:
- TOML parsing and validation
- Multi-location config loading
- Default value provision
Configuration Options:
- Add fields to
internal/config/config.gostructs - Update
commonDefaultConfig()with shared defaults; add platform-specific defaults tointernal/config/config_<os>.go - Add validation in
Validate*()methods - Update
configs/examples anddocs/CONFIGURATION.md
Navigation Modes:
- Define domain entities in
internal/core/domain/ - Create service in
internal/app/services/ - Implement infrastructure in
internal/core/infra/ - Add components in
internal/app/components/ - Implement the
Modeinterface ininternal/app/modes/(seeHintsMode,GridMode,ScrollModefor examples)- See also
RecursiveGridModefor recursive grid navigation
- See also
- Register mode in the Handler's mode map in
internal/app/modes/handler.go
Actions:
- Define action in
internal/core/domain/action/ - Implement logic in
internal/app/services/action_service.go - Add handling in
internal/app/modes/actions.go - Update config and documentation
UI Components:
- Create components in
internal/app/components/ - Implement rendering in
internal/ui/ - Add macOS-specific Objective-C in
internal/core/infra/platform/darwin/with a//go:build darwintag; provide a no-op stub for other platforms - Register in
internal/app/component_factory.goorinternal/app/app_initialization.go
CLI Commands:
- Create command file in
internal/cli/ - Register in
internal/cli/root.go - Document in
docs/CLI.md
Neru uses manual dependency injection for better testability and explicit dependency management:
- Construction: Dependencies are explicitly passed to constructors
- Wiring:
internal/app/app_initialization.gowires all components together - Testing: Dependencies can be mocked by passing test doubles in
NewWithDeps - Ports: Interfaces define contracts between layers
Example of dependency injection in action:
// In internal/app/initialization.go
hintService := services.NewHintService(accAdapter, overlayAdapter, systemPort, hintGen, cfg.Hints, logger)
gridService := services.NewGridService(overlayAdapter, systemPort, logger)
actionService := services.NewActionService(accAdapter, overlayAdapter, systemPort, logger)- Fork and clone the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make changes following Coding Standards
- Add tests for new functionality
- Test thoroughly:
just test && just lint && just build - Commit conventionally:
git commit -m "feat: description" - Push and open PR with description and screenshots
- Read the Architecture: Understand layered design and code placement
- Check Existing Issues: Search for similar work or start discussions
- Follow Standards: See CODING_STANDARDS.md
- Write Tests: All new code needs appropriate test coverage
- Update Docs: Keep documentation current with changes
All code must follow the Coding Standards document. See Testing Standards for test requirements.
Pre-commit Checklist:
- Code formatted (
just fmt) - Linters pass (
just lint) - Tests pass (
just test) - Build succeeds (
just build) - Documentation updated
- Follows CODING_STANDARDS.md
Key Requirements:
- Use
goimportsfor import organization - Add godoc comments for exported symbols
- Use custom error package with proper wrapping
- Follow established naming patterns and receiver conventions
All new code requires appropriate tests. See CODING_STANDARDS.md for detailed guidelines.
Test Types:
- Unit Tests: Business logic, algorithms, validation (fast, no system deps)
- Integration Tests: Real platform APIs, file system, IPC (tagged
//go:build integration && <os>)
When to Use:
- Unit Tests: Business logic, config validation, component interfaces, pure algorithms
- Integration Tests: Platform APIs, file operations, IPC, component coordination
Test Organization:
package_test.go # Unit tests (logic, mocks)
package_integration_darwin_test.go # macOS integration tests //go:build integration && darwin
package_integration_linux_test.go # Linux integration tests //go:build integration && linux
- Update docs/ for significant changes
- Add godoc comments for exported symbols
- Keep docs consistent with code changes
- Include examples where helpful
Use clear, descriptive commit messages following conventional commits:
Format: <type>(<scope>): <subject>
Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, etc.)refactor: Code refactoringperf: Performance improvementstest: Adding or updating testschore: Build process, dependencies, etc.
Good:
feat: add grid-based navigation mode
Implement grid-based navigation as an alternative to hint-based navigation.
Grid mode divides the screen into cells and allows precise cursor positioning.
Closes #123
Bad:
fix bug
Releases are being handled via Release Please automatically.
Neru uses semantic versioning: vMAJOR.MINOR.PATCH
- MAJOR - Breaking changes
- MINOR - New features (backward compatible)
- PATCH - Bug fixes
Creating a release is just as easy as merging the release please PR, and it will build and publish the binaries on github.
Note
Homebrew version bump is in a separate repo, it will be updated separately.
# Build and run
just build && ./bin/neru launch
# Watch for changes (requires entr)
ls **/*.go | entr -r sh -c 'just build && ./bin/neru launch'# Enable debug logging in config
[logging]
log_level = "debug"
# Watch logs (macOS)
tail -f ~/Library/Logs/neru/app.log
# Watch logs (Linux)
tail -f ~/.local/state/neru/log/app.log# Code quality
just fmt # Format code
just lint # Run linters
just test # Run tests
# Dependencies
go mod tidy # Clean up modules
go get -u ./... # Update dependencies- Read existing code - Well-structured codebase
- Check issues - Search for similar problems
- Ask in discussions - Open GitHub discussion for questions
- Open draft PR - Get early feedback on approach
- Go Documentation: https://golang.org/doc/
- macOS Accessibility: https://developer.apple.com/documentation/applicationservices/ax_ui_element_ref
- TOML Spec: https://toml.io/
- Cobra CLI: https://github.com/spf13/cobra
- Just Command Runner: https://github.com/casey/just