Contributing to Limpid

June 7, 2026 · View on GitHub

Thanks for your interest! Limpid is in pre-alpha, so things move fast and APIs are unstable.

By participating, you agree to follow the Code of Conduct.

Before you start

  • For non-trivial changes, open an issue first to discuss the approach.
  • Bug fixes and small improvements: PR directly is fine.

Setup

Prerequisites

  • macOS 26 (Tahoe) or later
  • Xcode 26 with the Metal Toolchain component
    xcodebuild -downloadComponent MetalToolchain
    
  • Homebrew
  • XcodeGen and Zig 0.15.2
    brew install xcodegen zig@0.15
    
    zig@0.15 is keg-only; the build script invokes it by its full path (/opt/homebrew/opt/zig@0.15/bin/zig), so no PATH change is required.

Build

# 1. Clone with submodules (Ghostty lives at vendor/ghostty)
git clone --recursive https://github.com/nek0der/limpid.git
cd limpid

# If you already cloned without --recursive, pull the submodules
# now — `make ghostty` fails on an empty vendor/ghostty/ checkout:
#   git submodule update --init --recursive

# 2. Confirm Zig is reachable (the build script hard-codes the
# Homebrew keg path, so missing zig@0.15 yields a clear error
# rather than silently picking up a system Zig):
#   ls /opt/homebrew/opt/zig@0.15/bin/zig

# 3. Build libghostty as an xcframework (10–20 min on first run; cached after)
make ghostty

# 4. Generate the Xcode project
make xcodegen

# 5. Build + launch (Debug)
make dev

make help lists every available target (build, run, test, dmg, screenshot, clean, …).

You should see a Limpid window with a working terminal pane (zsh by default), a sidebar, and the tab list. The embedded libghostty version is logged via os_log under the dev.limpid subsystem (log stream --predicate 'subsystem == "dev.limpid"').

First-launch tip: a locally-built Debug binary is ad-hoc signed, so macOS may quarantine it. Right-click → Open the first time.

Open in Xcode

make xcodegen
open Limpid.xcodeproj

Limpid.xcodeproj is gitignored — always regenerate from project.yml.

Language policy

  • Source code, comments, and doc strings: English only.
  • User-facing documentation (README.md, CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md): English.
  • Internal design documents under docs/: Japanese is the convention. Mixed-language research notes (e.g. key-handling-research.md) are kept as-is; new docs follow this rule.
  • Chat, commit message bodies, and PR descriptions: either language is fine. The commit summary line stays English so git log reads cleanly.
  • In-app strings are bilingual. Every user-visible label, tooltip, alert, and accessibility annotation has to land in Limpid/Resources/Localizable.xcstrings with both an en and a ja entry. English-only strings are treated as incomplete at review time — there is no CI gate, only the PR checklist and the maintainer's eye.

Pull requests

  • Branch off main using a conventional prefix: feat/<short>, fix/<short>, docs/<short>, chore/<short>, refactor/<short>, test/<short>.
  • Keep commits focused. Use Conventional Commits (feat:, fix:, docs:, etc.).
  • Code comments must be in English.
  • Code style: see AGENTS.md §3 for the conventions a linter can't enforce (comments explain why, file banners, naming).
  • CHANGELOG.md is auto-generated by release-please from commit messages — don't add it by hand.

Before opening the PR

Run the following from the repo root and make sure all pass:

make ghostty      # Only if vendor/ghostty/macos/GhosttyKit.xcframework is
                  # missing (fresh clone). `make test` does not rebuild it
                  # and will fail at link time without the xcframework.
make xcodegen     # Regenerate the Xcode project from project.yml.
make test         # Build + run the full test suite.
make lint         # swiftformat --lint + swiftlint --strict.

make help lists every available target.

If you are editing vendor/ghostty/ content, you're probably on the wrong path — that's a pinned upstream submodule. Bump it by updating the submodule ref, never by editing in place.

Opening the PR

Create the PR with gh pr create --body-file <file> so .github/pull_request_template.md is honored verbatim. The template has four substantive sections (Summary, Related issue, How verified, Screenshots/video) plus a checklist; fill each one, and write Skip. literally when a section genuinely doesn't apply (e.g. an empty Related issue). gh pr create --fill and the GitHub web UI both skip the template silently, which is the single most common first-PR review nit.

Tests

  • New tests use Swift Testing (import Testing, @Test, #expect). Existing XCTest tests are kept and migrated opportunistically — don't mix the two styles in one file.
  • Reuse fixtures from LimpidTests/Support/ (RepoFixture, TempGitRepo, WithTempDir, FakeGit, WindowSessionFixture, Tags) instead of rolling your own.
  • Smoke tests that touch the local filesystem or shell out to git should gate themselves on RepoFixture.hasLocalRepo so they no-op cleanly outside the source tree.
  • Never write to ~/Library/Application Support/Limpid/ from a test. Production stores (SessionStore, NotificationHistoryStore) accept an init(directory:) override; pair it with WithTempDir so each test runs against an isolated temp directory. Real user data has been corrupted by tests that called the no-arg init() and then clearAll().

Regenerating the hero screenshot

The .github/assets/hero.png shown at the top of README.md is captured from the app launched in demo mode. To refresh it after a UI change:

make screenshot   # Release build → launch in demo mode → capture → quit.
                  # Output lands at .github/assets/hero.png.

The script sets LIMPID_DEMO=1 so the app loads DemoFixture instead of the user's real state.json — persistence is disabled while the env var is set.

One-time setup: grant your terminal Screen Recording permission so the script can both query Limpid's window bounds via CGWindowListCopyWindowInfo and capture the pixels via screencapture (one permission covers both).

System Settings → Privacy & Security → Screen Recording → click + → add your terminal app (Terminal, Ghostty, etc.) → toggle on.

If Tab, SplitTree, or SessionSnapshot shapes change, run DemoFixtureTests first; the fixture is anchored on stable UUIDs and a JSON round-trip.

AI tools

Using AI assistants (Claude, Copilot, Cursor, etc.) is welcome. Please be able to explain what your PR does without leaning on the tool.

See AGENTS.md for the conventions agents (and humans) should follow inside the repository.

Reporting security issues

See SECURITY.md.

Questions

Open a Discussion or an issue.