Skip to content

Latest commit

 

History

History
366 lines (284 loc) · 13.4 KB

File metadata and controls

366 lines (284 loc) · 13.4 KB

Contributing to via

Thanks for your interest in via! via is an experimental Rust terminal application that hosts Neovim and AI coding agents side-by-side in a single native window, using libghostty's VT engine for rendering and input. It emphasizes easy integration between agent and nvim while staying out of the way of the developer

Code of Conduct

Be kind, assume good intent, and keep discussions technical. We are a small project; constructive feedback is appreciated.

Prerequisites

You will need:

  • A recent stable Rust toolchain (the project uses Rust 2024 edition).
  • Zig 0.15.2 — required to build the vendored libghostty-vt terminal engine at compile time.
  • git — the libghostty-vt-sys build script uses it to fetch Ghostty sources.
  • On Linux (the primary development and release target): system development libraries for fonts, Wayland/X11, and input.

System libraries (Debian/Ubuntu example, matching CI)

sudo apt-get update
sudo apt-get install -y \
  libfontconfig1-dev \
  libfreetype6-dev \
  libwayland-dev \
  libx11-dev \
  libx11-xcb-dev \
  libxcb1-dev \
  libxcb-render0-dev \
  libxcb-shape0-dev \
  libxcb-xfixes0-dev \
  libxkbcommon-dev \
  pkg-config

Zig + tools via mise (recommended)

The project pins Rust 1.93 (MSRV), Zig, and rust-analyzer in mise.toml:

mise install

The mise.toml also defines handy tasks (run with mise <task> or mise run <task>):

mise test
mise fmt
mise clippy
mise dev     # foreground run (no detach)
mise bench
mise build
mise clean

List available tasks with mise tasks. These activate the pinned tools (Rust, Zig, etc.) for you — so mise build uses the project toolchain even if your default rustup toolchain is older.

Otherwise, install Rust ≥ 1.93 and Zig 0.15.2 manually and ensure they are on PATH.

Building

cargo build --release
./target/release/via

libghostty-vt is statically linked; no extra runtime .so search paths are needed.

First builds are slow. The dependency libghostty-vt = { git = "...", rev = "..." } (see Cargo.toml) triggers a checkout of Ghostty sources + a Zig build of the VT core during cargo build. Results are cached under target/.

For local iteration without detach (Linux detaches by default to free the terminal):

VIA_FOREGROUND=1 cargo run -- ...
# or, if using mise:
mise dev

Testing, formatting, and linting

With mise (recommended if you're already using it for Zig):

mise test
mise fmt
mise clippy

Raw cargo commands (always work):

cargo test                 # unit tests (co-located with sources under #[cfg(test)])
cargo fmt -- --check
cargo clippy -- -D warnings

Neovim Lua tests (the :ViaTasks PM UI and other nvim/ integration):

scripts/test-nvim.sh       # headless nvim; requires nvim >= 0.9 for `-l`
# or: mise run test-nvim

These live under nvim/tests/ and use a tiny built-in harness (no busted/plenary). Pure helpers (parse_row, format_row, build_content) are tested directly; board actions and the task-body float are tested with the command runner (M.run) stubbed, so no real via binary or live session is needed. CI runs this as the nvim lua tests job.

We use:

  • rustfmt.toml at the root (edition + a few style choices).
  • clippy.toml (currently minimal; MSRV will be recorded here later).

All tests live in src/** as #[cfg(test)] mod tests. There is no separate tests/ crate today (the combination of native winit window + PTYs + Neovim makes fully hermetic GUI integration tests expensive to maintain).

For a live-session orchestration smoke test, start via with an ACP-capable primary agent such as opencode, then run this from a via-launched pane:

scripts/e2e-agent-orchestration.sh
# or: mise run e2e-agents

The script is intentionally opt-in, not CI-gated. It drives real via agent commands against the active VIA_SESSION: spawns reviewer and orchestrator, checks ACP registry mode, verifies ACP prompt delivery, verifies PTY mailbox-only send behavior, checks missing-recipient failure, and terminates spawned panes.

For a higher-level skills / harness eval (create a task board, delegate to helpers, implement a tiny fixture until green), see evals/task-board-loop/:

# After an agent run (portable: paste PROMPT.md into any harness):
EVAL_BOARD_ID=eval-<run-id> mise run eval-task-board-grade

# Or unattended inside a live via session (ACP spawn required):
mise run eval-task-board-via

Also opt-in and not CI-gated. Grading is deterministic (grade.sh + fixture/verify.sh); there is no LLM judge.

We have Criterion micro-benchmarks for the two most user-visible performance-sensitive surfaces:

  • Layout calculations (src/ui/ghostty/layout.rs): split decisions, column reservation, focus-after-reference.
  • Link / OSC 8 / symbol scanning (src/ui/ghostty/links.rs): reference_target_from_row and URI variants, used for Ctrl+click routing and Ctrl-held clickable cues in visible agent output.

If your change touches layout math or the reference scanner, run cargo bench locally (before/after) and include a short summary or target/criterion/ report link in the PR description. The CI runs a quick, non-blocking informational cargo bench pass on every PR for visibility (see .github/workflows/ci.yml); it uses reduced sample counts so it finishes in reasonable time. For serious measurements use the defaults on your machine.

cargo bench (or mise bench) requires the same system libs + Zig as a normal build.

Running a full via session locally

via launches Neovim in one pane and your agent in another (or a single-pane ACP layout).

ACP agents (spawned orchestration / helpers):

VIA_FOREGROUND=1 cargo run -- --agent opencode
via agent spawn --id orchestrator --role orchestrator

Normal mode / PTY agents (primary interactive pane; context injected via PTY):

VIA_FOREGROUND=1 cargo run -- --agent claude

In Neovim:

  • <leader>ab or :ViaBufferSend — explicitly send the current buffer (or visual selection) to the primary PTY agent pane. For ACP helpers, use via agent send --to <id> or Lua require('via').agent.send.
  • via session refresh [--file PATH] — ask Neovim to reload externally changed buffers after agent edits. via also runs :checktime on FocusGained and BufEnter.
  • Hold Ctrl in the agent output to highlight clickable filenames, symbols, and OSC 8 hyperlinks. Ctrl-click file paths or symbol://Foo::bar references to open them in the Neovim pane; Ctrl-click external OSC 8 hyperlinks to open them in the system browser.

See the main README.md for configuration (~/.config/via/via.conf, env vars, --agent-pane-cols, review backends, font tweaks, etc.) and the embedded Neovim Lua bridges (nvim/*.lua).

Architecture pointers

  • Original lightweight design brief: project.md
  • ACP investigation and status: acp.md
  • Core runtime pieces (read these for context before touching):
    • src/mediator.rs — Tokio-based event router. Handles Neovim RPC events, ACP client, editor state (src/editor.rs), LSP bridge, PTY output, and UI commands.
    • src/ui/ghostty.rs (and src/ui/ghostty/*) — winit event loop + libghostty-vt surfaces, pane layout/splits/fullscreen, input routing, OSC 8 link extraction, Ratatui-backed ACP pane, review terminal toggling (hunk or nvim), font rendering with cosmic-text.
    • src/acp.rs — small ACP JSON-RPC client (initialize, new_session, prompt, tool results).
    • src/nvim.rs + nvim/ — nvim-rs RPC client + embedded Lua templates for file/symbol open, diagnostics export, review.
    • src/config.rs, src/session.rs, src/cli/ — layered config (CLI > env > via.conf > defaults), session manifests (for cross-process via session * commands), subcommand implementations.
    • src/pty.rs — portable-pty wrapper with coalesced output notification.
    • src/lsp_bridge.rs — Unix socket bridge allowing the agent (via the skill) to perform LSP requests in the live Neovim session.

Key invariants the tests and benches protect:

  • Layout must always reserve at least the editor minimum columns; agent pane respects its min/max range.
  • Clicking references (file + optional :line, or symbol URIs) must be fast and focus the editor pane appropriately.
  • Explicit context for the primary pane is :ViaBufferSend; ACP helpers use targeted via agent send --to <id> / Lua agent.send.

Good places to start contributing

  • Pane layout and resize edge cases (src/ui/ghostty/layout.rs and its tests).
  • Link/symbol scanning robustness and performance (src/ui/ghostty/links.rs).
  • Completing the ACP TUI UX (model/mode selection, tool call rendering, diff preview, styling) — see src/acp_tui/, src/ui/ghostty/acp_modal.rs, and mediator.rs.
  • Review backend improvements or the "hunk" backend.
  • Diagnostics / session CLI ergonomics and the Lua side (nvim/diagnostics.lua, src/cli/session.rs).
  • Making the first-time build experience better (docs, caching, clearer errors from the ghostty build script).
  • More tests, especially property-style or boundary cases for layout and link parsing; Criterion benchmarks for the two hot paths (layout math and per-row reference scanning).
  • Documentation, examples, and contributor onboarding.

If you're touching layout math or the main reference scanner, please add or update a benchmark.

Manual testing: mock ACP agent

The mock_acp_agent cargo example is a controllable fake ACP agent for exercising the permission modal queue UX (pending badge, Tab/Shift+Tab navigation, FIFO drain, terminate-with-pending) without a real backend.

Build the runnable example binary (the test target has a hashed filename):

cargo build --example mock_acp_agent

Spawn it as an ACP pane (use an absolute path; the trailing acp token is required so via classifies the pane as ACP, not PTY):

via agent spawn --id mock1 --command "$(pwd)/target/debug/examples/mock_acp_agent acp"

After handshake, the mock prints an interactive menu in the ACP pane transcript. Type commands directly in the ACP TUI pane, or send the same text with via agent send --to mock1 -m '<command>' (both arrive as session/prompt once the spawn command is ACP-classified).

Commands:

  • 1 or burst [N] [delay_ms] — fire N permission modals concurrently; the prompt stays pending until all complete.
  • 2 or drip [N] [delay_ms] — one modal at a time; the next starts only after the previous fake command finishes (reproduces a modal arriving while another is on screen).
  • help — show the menu again.

Omitted N and delay_ms use clap defaults (--requests, default 3; --delay-ms, default 0). Unknown or malformed input prints a concise help/error transcript and ends the turn without firing requests.

Each fake command emits a full tool-call lifecycle: tool_call pending → session/request_permission → (after the user answers) in_progress → completed or a terminal failure — nothing is executed on the host. Fake commands use demo-command-<n> --not-auto-approved in tool metadata so via's auto-approve policy does not skip the modal. Each resolution is echoed as an agent_message_chunk transcript line (mock: request <toolCallId> resolved -> <optionId|cancelled>). Tool-call and message IDs increase monotonically across repeated prompts; a second drip is rejected while one is active. session/cancel cancels in-flight fake tool calls and completes the prompt with stopReason=cancelled.

UX checklist when testing manually:

  1. burst or burst 3: modal shows (+2 pending) badge; Tab/Shift+Tab cycles without answering; answering drains FIFO and echoes resolve lines; prompt stays pending until all N complete.
  2. drip 3: first modal visible; after answering (and fake tool completion), the next appears (exercises queue push while UI is active).
  3. Retrigger burst after a burst: new modals get fresh monotonic tool-call IDs.
  4. Start drip while a drip is active: transcript shows rejection, no new modals.
  5. Cancel (session/cancel / pane cancel): in-flight fake tool calls terminalize; prompt completes with stopReason=cancelled.
  6. Terminate the pane with pending modals: queue clears without hanging.

Submitting a pull request

  1. Fork the repo and create a topic branch.

  2. Make your change + tests.

  3. Run locally:

    cargo fmt -- --check
    cargo clippy -- -D warnings
    cargo test

    (If adding layout/links changes: also cargo bench / mise bench and note before/after numbers in the PR.)

  4. Push and open a PR against main.

  5. In the PR description, explain the motivation, user impact, and any performance or behavior changes. Link related issues.

CI (once added) will enforce formatting, clippy, and tests on Linux. The release workflow remains separate (triggered by GitHub releases).

Small, reviewable PRs are strongly preferred. We can always iterate.

Release process (for maintainers)

  • Create a GitHub release with a tag like v0.3.0.
  • The existing .github/workflows/release.yml builds on ubuntu-24.04, packages the binary + README into a via-<tag>-linux-x86_64.tgz (plus SHA256), and uploads the assets.
  • Currently only linux-x86_64 is produced. Cross-platform binaries or source builds for macOS/Windows are future work.

Questions?

Open a GitHub issue or discussion. Thank you for helping make via better!