Contributing
This guide covers setting up a development environment, project architecture, and coding conventions.
Development setup
Section titled “Development setup”Kyaraben uses a Nix flake for its development environment. If you have Nix installed:
nix developThis provides Go, Node.js, golangci-lint, and other development tools.
Alternatively, install these manually:
- Go 1.24+
- Node.js (for the UI)
- golangci-lint
- just (task runner)
Install dependencies:
cd ui && npm ciRunning locally
Section titled “Running locally”just dev # Run the Electron app in development modejust test # Run Go testsjust lint # Run Go linterjust fmt # Format all codejust check # Run all checks (lint + test + typecheck)just site-dev # Run the documentation site locallySee just --list for all available tasks.
Languages and typing
Section titled “Languages and typing”The backend and CLI are Go. The UI is TypeScript (Electron + React). Both languages offer static typing, which the project relies on heavily.
Go types in internal/daemon/types.go are the source of truth for the daemon
protocol. TypeScript types are generated from Go types using tygo to ensure the
contract between components is enforced at compile time.
Testing
Section titled “Testing”The project follows Martin Fowler’s distinction between fakes and mocks. Fakes are working implementations with shortcuts (an in-memory store instead of a real database). Mocks verify that specific methods were called with specific arguments, which couples tests to implementation details. Prefer fakes.
Test harnesses matter more than individual test cases. A well-designed harness that can spin up isolated environments, run commands, and assert on outcomes makes writing new tests trivial.
- Unit tests cover pure logic
- Integration tests use fakes for external dependencies
- E2E tests invoke the real system
For UI E2E tests, follow the
Playwright best practices. Prefer
user-facing selectors like getByRole, getByLabel, and getByText.
Project architecture
Section titled “Project architecture”The backend is a Go daemon that communicates with the TypeScript UI via JSON over stdin/stdout.
Domain model
Section titled “Domain model”The core entities and their relationships:
System: a gaming platform (SNES, PSX, GameCube)Emulator: an implementation that runs a system’s games (retroarch:bsnes, duckstation)Provision: a user-provided file enabling emulation (BIOS, keys)State: data an emulator produces (saves, savestates, screenshots)EmulatorConfig: a configuration file for an emulatorKyarabenConfig: Kyaraben’s own configuration (config.toml)Collection: where the user’s emulation data lives (~/Emulation)Manifest: tracks what Kyaraben has installed and managed
Daemon protocol
Section titled “Daemon protocol”The UI and CLI communicate through a daemon that handles long-running operations. The daemon uses JSON request-response over stdin/stdout.
Commands: apply, status, doctor, get_config, set_config,
get_systems, preflight, sync_status, uninstall, and others.
Events: ready, result, progress, error, cancelled.
Adding systems and emulators
Section titled “Adding systems and emulators”See .claude/skills/adding-emulator-support/SKILL.md for the complete process,
including decision frameworks, code examples, provision strategies, and
troubleshooting.
Integration principles
Section titled “Integration principles”The goal is that users enable a system, run apply, and the emulator is immediately ready to use with paths pointing to the user store. If the user has data from another Kyaraben installation (ROMs, saves, BIOS files in the expected locations), Kyaraben should pick them up without reconfiguration.
Kyaraben assumes it is the only tool installing these emulators. If the user has existing installations from their package manager or other tools, Kyaraben may conflict with them. When Kyaraben uninstalls a system, it removes the emulator configuration it created.
The first-run experience should be “launch and play”, not “configure before you can play”. Suppress setup wizards and first-run prompts where possible.
When an emulator does not fully cooperate with Kyaraben’s configuration model, handle the workaround in the implementation:
- Emulators that lack config options for certain paths need symlinks from their fixed data locations to the user store
- Emulators that require user interaction (initial setup, BIOS installation through the emulator’s own UI) should surface this through the provisions system: the UI shows users where to place files, opens directories on click, and install-check provisions verify that required actions were completed
- Game library configuration varies: some emulators can be pre-configured with ROM directories, others require the user to manually browse to them
The goal is that users can discover and resolve issues from within Kyaraben (UI or CLI) or the emulators themselves, without needing external documentation.
Emulator installation methods
Section titled “Emulator installation methods”When adding support for an emulator, follow this priority order:
- Check for official versioned binary releases (AppImage preferred, then tarball, then plain binary). The URL must support version substitution for reproducible downloads.
- If no official binary exists, check for well-maintained unofficial builds (actively maintained, multiple contributors, builds from official source).
- For RetroArch cores, use the RetroArch buildbot archive.
- If no viable option exists, skip the system and document the blocker.
Rejected formats: Flatpak and Snap require system integration we do not want to force on users. Distribution packages (.deb, .rpm) could theoretically work by extracting their assets, but AppImages are preferred for simplicity.
Emulator path configuration
Section titled “Emulator path configuration”When integrating an emulator, follow this priority order for configuring paths:
- Text-based configuration: if the emulator has a config file where paths can be set (INI, JSON, XML, TOML, etc.), use that. Point paths directly to the user store. This is the cleanest approach.
- Symlinks: if the emulator stores data in fixed locations that cannot be
configured via text, learn the directory structure and create symlinks from
those locations to the user store. Return symlink specs in
GenerateResult.
Symlinks work by redirecting specific subdirectories from the emulator’s data location to Kyaraben’s standard locations:
~/.local/share/dolphin-emu/GC/ → ~/Emulation/saves/gamecube/~/.local/share/dolphin-emu/Wii/ → ~/Emulation/saves/wii/~/.local/share/dolphin-emu/StateSaves/ → ~/Emulation/states/dolphin/~/.local/share/dolphin-emu/ScreenShots/ → ~/Emulation/screenshots/dolphin/When symlinks work, the emulator runs identically to a manual installation.
Prefer this over CLI flags like -u <dir> which create Kyaraben-specific
launch commands that diverge from default behavior and complicate debugging.
Testing emulator support
Section titled “Testing emulator support”Emulator integration requires manual testing on real hardware. Automated tests verify config generation, but only hardware testing catches issues with paths, controllers, hotkeys, and first-run behavior.
When to test
Section titled “When to test”- Adding a new emulator
- Upgrading an emulator version
- Changing config generation logic
- Modifying path or symlink handling
Test hardware
Section titled “Test hardware”- Steam Deck (primary target)
- Desktop Linux (secondary)
- Controllers: Xbox-style (full features) and SNES USB (limited buttons)
Pre-testing checks
Section titled “Pre-testing checks”Before manual testing, run automated checks to catch obvious issues:
just check # Lint, test, typecheckFeature parity reference
Section titled “Feature parity reference”Not all emulators support all features. See the emulator support table for what each emulator supports. Use it to know what to test and what to skip.
Source files are in internal/emulators/<name>/. Each emulator’s definition
file contains:
StateKinds: which data types are synced (saves, states, screenshots)ProvisionGroups: BIOS/firmware requirementsConfigGenerator.Generate(): config patches, symlinks, and controller bindings
Path configuration testing
Section titled “Path configuration testing”Verify paths by checking where files actually land after gameplay:
- Saves: play a game, save in-game, check
~/Emulation/saves/<system>/ - Savestates: create a savestate via hotkey (if supported), check
~/Emulation/states/<emulator>/ - Screenshots: take a screenshot via hotkey (if supported), check
~/Emulation/screenshots/<emulator>/ - BIOS: verify the emulator finds BIOS from
~/Emulation/bios/<system>/
For emulators using symlinks, verify the symlink exists and points to the
correct user store location. Symlink specs are returned from each emulator’s
ConfigGenerator.Generate() method.
Controller testing
Section titled “Controller testing”For emulators with Controller: configured in the support table, test:
- Face buttons: verify A/B/X/Y map correctly (some emulators swap layouts)
- D-pad: all four directions register
- Analog sticks: left and right sticks work (if system has them)
- Shoulders: L/R buttons register
- Triggers: LT/RT register (analog for systems that support it)
- Start/Select: both buttons work
For multiplayer (Players > 1), connect additional controllers and verify
each slot binds correctly.
Skip controller testing for emulators with Controller: auto (RPCS3, Vita3K);
these emulators handle controller detection internally.
First-run behavior testing
Section titled “First-run behavior testing”Verify kyaraben suppresses setup wizards and prompts:
- Delete the emulator’s config directory to simulate first run
- Run
kyaraben applyto regenerate config - Launch the emulator and verify no wizard or prompt appears
Check for these common issues:
- Update check prompts (should be disabled)
- Welcome dialogs (should be suppressed)
- Controller setup wizards (should be skipped)
- BIOS selection prompts (should use configured path)
Frontend testing
Section titled “Frontend testing”Verify ES-DE launches games correctly:
- Add a ROM to
~/Emulation/roms/<system>/ - Open ES-DE and navigate to the system
- Launch the game and verify the correct emulator opens
- Verify fullscreen mode works (if configured)
Dependency management
Section titled “Dependency management”Pass dependencies explicitly. No hidden instantiation deep in the call stack. Expensive instantiations happen at the composition root (main.go) and dependencies are threaded through constructors.
Define dependencies as interfaces where substitution is needed. Swap real implementations for fakes at construction time.
Logging
Section titled “Logging”The Go daemon writes to ~/.local/state/kyaraben/kyaraben.log. Use the
logging package for all Go log output:
var log = logging.New("apply")
func DoSomething() { log.Info("starting operation") log.Debug("details: %v", details) log.Error("operation failed: %v", err)}Log levels: Debug (detailed debugging info), Info (significant events),
Error (error conditions).
Code style
Section titled “Code style”Make code self-evident. Do not write comments explaining what code does; if the code needs explanation, rewrite it to be clearer.
Use sentence-case in headings. Do not use bold text in documentation. Avoid em-dashes. No emoji.
Design principles
Section titled “Design principles”Kyaraben should feel calm. Do not use language that implies urgency or alarm. Features should have few moving parts. The user should never feel nervous because Kyaraben is explicit about what is happening and what has happened.
Avoid phrases like “needs attention”, “action required”, or “warning” when a neutral description suffices. Instead of “3 files need attention”, say “3 local files” or “3 files only on this device”. State facts; let the user decide if action is needed.
Commit messages
Section titled “Commit messages”The format is a brief actionable title in imperative mood, followed by a body explaining what changed and why, followed by a test plan:
Brief, actionable description
What changed and why. Use paragraphs, lists, or both.
## Test plan
Reproducible verification steps.Use imperative mood in titles: “Add”, “Fix”, “Remove”. No trailing periods on list items. Use backticks for code references. Do not use bold text.