diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..5ae0eec --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[alias] +xtest = "nextest run --workspace --all-features" +lint = "clippy --workspace --all-targets --all-features -- -D warnings" diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 0000000..aa15a82 --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,10 @@ +[profile.default] +fail-fast = false +failure-output = "immediate-final" +slow-timeout = { period = "30s", terminate-after = 2 } +[profile.ci] +fail-fast = false +retries = 0 +status-level = "pass" +final-status-level = "slow" +slow-timeout = { period = "30s", terminate-after = 2 } diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..7e26266 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI + +on: + push: + pull_request: + +jobs: + rust: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install pinned Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.94.1 + components: rustfmt, clippy + + - name: Format + run: cargo fmt --all --check + + - name: Lint + run: cargo clippy --workspace --all-targets --all-features -- -D warnings + + - name: Test + run: cargo test --workspace --all-features diff --git a/AGENTS.md b/AGENTS.md index efde112..635890c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,8 +37,9 @@ workspace's `Research/` directory. They are references, not vendored code. Before committing Rust changes, run: ```bash -cargo fmt --all --check -cargo clippy --workspace --all-targets --all-features -- -D warnings -cargo test --workspace --all-features +./scripts/ci.sh ``` +Keep `bacon` running during development. Tests must use synthetic fixtures; real +agent transcripts, subscription state, private source, and credentials are never +test data. diff --git a/README.md b/README.md index 37ccc45..c6c43a1 100644 --- a/README.md +++ b/README.md @@ -37,11 +37,17 @@ contracts before committing to a large implementation. Start with [the product spec](docs/PRODUCT_SPEC.md), [architecture](docs/ARCHITECTURE.md), [research map](docs/RESEARCH.md), and -[captured upstream revisions](docs/RESEARCH_SNAPSHOTS.md). +[captured upstream revisions](docs/RESEARCH_SNAPSHOTS.md). The native UI choice +is intentionally open; see [the measured decision plan](docs/UI_OPTIONS.md). ## Scaffold ```bash -cargo test --workspace +bacon # continuous check; t tests, c lints, v runs the full gate +cargo xtest # fast isolated tests with nextest +./scripts/ci.sh # format + strict Clippy + tests + doctests cargo run -p lumbridge ``` + +See [the testing strategy](docs/TESTING.md) for fake harnesses, ACP replay, +terminal conformance, UI driving, recovery, performance, and packaging tests. diff --git a/bacon.toml b/bacon.toml new file mode 100644 index 0000000..f6c5dce --- /dev/null +++ b/bacon.toml @@ -0,0 +1,45 @@ +#:schema https://dystroy.org/bacon/.bacon.schema.json + +default_job = "check" +env.CARGO_TERM_COLOR = "always" + +[jobs.check] +command = ["cargo", "check", "--workspace", "--all-targets", "--all-features"] +need_stdout = false + +[jobs.clippy] +command = [ + "cargo", "clippy", "--workspace", "--all-targets", "--all-features", + "--", "-D", "warnings", +] +need_stdout = false + +[jobs.test] +command = [ + "cargo", "nextest", "run", "--workspace", "--all-features", + "--hide-progress-bar", "--failure-output", "final", +] +need_stdout = true +analyzer = "nextest" + +[jobs.doc-test] +command = ["cargo", "test", "--workspace", "--all-features", "--doc"] +need_stdout = true + +[jobs.run] +command = ["cargo", "run", "-p", "lumbridge"] +need_stdout = true +allow_warnings = false +background = true +show_command_error_code = true + +[jobs.ci] +command = ["./scripts/ci.sh"] +need_stdout = true + +[keybindings] +c = "job:clippy" +t = "job:test" +d = "job:doc-test" +r = "job:run" +v = "job:ci" diff --git a/crates/lumbridge-core/tests/public_contract.rs b/crates/lumbridge-core/tests/public_contract.rs new file mode 100644 index 0000000..c24c1cd --- /dev/null +++ b/crates/lumbridge-core/tests/public_contract.rs @@ -0,0 +1,10 @@ +use lumbridge_core::{Platform, ProductStatus, UsageProvenance}; + +#[test] +fn downstream_crates_can_use_the_public_scaffold_contract() { + let status = ProductStatus::architecture_phase(Platform::ArchLinux); + + assert_eq!(status.platform.to_string(), "Arch Linux / Omarchy"); + assert!(status.message.contains("Architecture scaffold")); + assert_eq!(UsageProvenance::Estimated, UsageProvenance::Estimated); +} diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 0000000..b1af2bb --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,110 @@ +# Testing strategy + +Lumbridge controls shells, repositories, credentials, agents, and long-lived +processes. A test plan built only from UI clicks would be slow, flaky, and blind +to the failures that matter. The product is designed with programmatic testing +surfaces from the start. + +## The central seam + +The desktop UI and the test driver use the same versioned local IPC commands and +events exposed by the session runtime. UI tests can therefore: + +1. start an isolated runtime with a temporary data directory; +2. issue real commands such as create workspace, split pane, launch harness, + resize PTY, approve tool, cancel turn, and restore session; +3. wait on semantic events rather than sleeps; +4. query a redacted state snapshot; and +5. separately confirm the visible/accessibility representation where needed. + +The test API is not a back door. It uses the same validation and state machine as +the UI, binds locally, requires a per-run capability token, and is absent from +release builds unless explicitly enabled. + +## Test layers + +### Pure and property tests + +Layouts, terminal grid transitions, usage windows, forecasts, event reduction, +path rules, and capability negotiation are deterministic functions. Test them +exhaustively without starting a window. Property tests should target split-tree +invariants, event replay, Unicode width, and usage aggregation. + +### Contract tests + +Every platform adapter implements the same suites for PTYs, process groups, +signals, resize, secret handles, notifications, paths, and atomic replacement. +Every harness adapter implements discovery, launch, resume, cancel, status, and +usage contracts. Unsupported capability is a valid explicit result. + +### Fake harnesses + +Small Rust fixture binaries behave like coding agents without network access or +subscriptions. Scripts cover partial frames, noisy stderr, permission prompts, +child processes, crashes, hangs, huge output, invalid bytes, and cancellation. +Tests assert both user-visible state and cleanup of the entire process tree. + +### ACP replay + +Synthetic ACP transcripts are replayed in both directions against the official +Rust SDK. Golden fixtures cover protocol versions and capability combinations. +Fuzzing targets frame decoding and state transitions; stdout must remain protocol +pure and diagnostics must stay on stderr. + +### Terminal conformance + +Byte-stream fixtures assert screen cells, styles, cursor, alternate screen, +scrollback, selection, hyperlinks, bracketed paste, keyboard modes, and Unicode. +PTY integration tests exercise real shells on each supported OS. Human typing +and agent automation are stressed concurrently to catch input-order corruption. + +### Persistence and recovery + +The runtime is interrupted after each durable event and external mutation, then +restarted. Tests prove accepted commands are not lost, incomplete work is +reconciled, migrations are forward-only, and corrupt/truncated tails fail safely. + +### UI and rendering + +Component tests drive semantic actions and accessibility identifiers. Golden +images cover a small set of high-value layouts at fixed fonts, scale, theme, and +GPU/software renderer. Snapshot updates require review; snapshots never replace +state assertions. + +### Performance gates + +Criterion and end-to-end probes record input-to-present latency, output ingest, +scrollback search, six-pane frame time, idle CPU, memory per pane, startup, and +restore. CI uses generous regression ceilings; dedicated metal/macOS runs retain +the detailed distributions. + +### Packaging smoke tests + +Fresh macOS, Ubuntu, and Omarchy/Arch environments install, launch, open a shell, +upgrade, roll back, and uninstall. Artifacts are checked for signatures, +checksums, SBOM, license notices, forbidden secrets, and source revision. + +## Developer loop + +```bash +# Continuous cargo check. Press t for nextest, c for strict Clippy, v for all. +bacon + +# Direct fast test run. +cargo xtest + +# Exactly what must pass before a commit. +./scripts/ci.sh +``` + +`cargo-watch` is no longer the project default because upstream archived it in +2025. `scripts/watch.sh` uses Bacon and retains a cargo-watch fallback for people +who already have the final cargo-watch release installed. + +## CI matrix as the project grows + +Every change runs format, Clippy, unit, contract, integration, and doctests on +Linux. Pull requests that touch platform/UI code also run macOS. Nightly jobs add +MSRV, coverage, dependency/license audit, fuzz smoke, renderer snapshots, soak, +and performance tracking. Release candidates must pass native packaging smoke on +macOS, Ubuntu, and Arch/Omarchy rather than treating cross-compilation as proof. diff --git a/docs/UI_OPTIONS.md b/docs/UI_OPTIONS.md new file mode 100644 index 0000000..d24d479 --- /dev/null +++ b/docs/UI_OPTIONS.md @@ -0,0 +1,96 @@ +# Native UI decision + +Lumbridge will not use Electron, React, TypeScript, or a webview as its +application shell. The shell, layout engine, terminal, session runtime, and +editor surfaces must be native Rust. An isolated browser pane may host the +operating system web engine because rendering arbitrary web pages is a browser +problem, not an application-shell architecture. + +This decision stays open until the finalists run the same workload. Choosing on +repository popularity or a polished demo would hide the risks that matter for a +terminal-heavy, accessibility-aware, cross-platform IDE. + +## Finalists + +### GPUI + +GPUI is the closest architectural match to Zed: retained state, GPU rendering, +text-heavy native interfaces, and an Apache-2.0 framework crate. It is the +highest-upside choice if Lumbridge wants Zed-like responsiveness and is willing +to track a fast-moving framework. + +Risks: + +- it is pre-1.0 and tightly shaped by Zed's needs; +- framework documentation and third-party component coverage are still young; +- the complete dependency and license closure must be audited before adoption; +- Zed's editor, terminal, and Markdown application crates are GPL-3.0-or-later, + so Lumbridge cannot copy those implementations into an Apache-2.0 release. + +### Floem + +Floem is the strongest independent comparator. It is MIT-licensed, pure Rust, +fine-grained and reactive, renders through wgpu/Vello or Skia, and is proven in +the Lapce editor. Its editor primitives may shorten the path to a native +Markdown/code surface without binding Lumbridge to Zed's application code. + +Risks: + +- it is also pre-1.0 and has a smaller ecosystem; +- current releases carry custom windowing integration that needs maintenance + review; +- accessibility, IME, embedded-browser parenting, and multi-window behavior + still need validation on every target desktop. + +## Viable fallback + +Iced is MIT-licensed, cross-platform, and renderer-agnostic with a wgpu path. It +has approachable architecture and broad examples, but its own documentation +still calls it experimental. It is a sound fallback if GPUI and Floem fail the +spike, though Lumbridge would build more IDE/editor infrastructure itself. + +## Useful building blocks, not the shell decision + +- `winit + wgpu + cosmic-text` gives maximum renderer control and is a sensible + direction for a dedicated terminal canvas. Owning an entire desktop toolkit, + editor, IME, accessibility layer, menus, drag-and-drop, and window integration + in the first release would be too much product risk. +- `egui` is excellent for internal diagnostics and rapid prototypes. Its + immediate-mode model and non-native visual conventions make it a weaker main + IDE shell. +- `wry` can host a tightly isolated system-web-engine child for browser panes: + WKWebView on macOS and WebKitGTK on Linux. It must not become a web frontend or + a privileged bridge into the session runtime. +- `AccessKit` should provide the semantic accessibility tree regardless of the + rendering framework. + +The Markdown experience remains native: a rope-backed editor, incremental +parsing and syntax trees, and a Rust Markdown renderer. Preview links and +embedded web content cross the browser boundary through a narrow, capability- +checked interface. + +## The decision spike + +Build the same disposable shell in GPUI and Floem before product implementation: + +- workspace sidebar, tab strip, split tree, command palette, footer, and six + fake busy terminal panes; +- native Markdown edit and preview surface; +- browser placeholder followed by one isolated system-web-engine child; +- semantic test identifiers, keyboard-only navigation, accessibility tree, IME, + copy/paste, drag-and-drop, and window restore; +- package and launch on macOS, Ubuntu, and Omarchy/Arch. + +Record, do not estimate: + +- cold and warm startup; +- idle RSS and RSS with six panes; +- key-to-present p50/p95 and output-to-present latency; +- frame time while all panes stream output; +- binary and installed size; +- accessibility/IME correctness and native window behavior; +- build/package friction and the complete dependency/license closure. + +Choose the framework whose measured result gives Lumbridge the best input and +streaming latency without failing accessibility, packaging, or maintainability. +If the results are close, prefer the smaller operational and licensing risk. diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..4870126 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.94.1" +components = ["clippy", "rustfmt"] +profile = "minimal" diff --git a/scripts/ci.sh b/scripts/ci.sh new file mode 100755 index 0000000..1b9d78b --- /dev/null +++ b/scripts/ci.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +cargo fmt --all --check +cargo clippy --workspace --all-targets --all-features -- -D warnings + +if command -v cargo-nextest >/dev/null 2>&1; then + cargo nextest run --workspace --all-features --profile "${NEXTEST_PROFILE:-default}" +else + echo "cargo-nextest not installed; using cargo test" >&2 + cargo test --workspace --all-features +fi + +# nextest deliberately does not run doctests on stable Rust. +cargo test --workspace --all-features --doc diff --git a/scripts/watch.sh b/scripts/watch.sh new file mode 100755 index 0000000..7330e57 --- /dev/null +++ b/scripts/watch.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +if command -v bacon >/dev/null 2>&1; then + exec bacon "$@" +fi + +# Compatibility for developers who still have the now-dormant cargo-watch. +if command -v cargo-watch >/dev/null 2>&1; then + exec cargo watch -x "check --workspace --all-targets --all-features" \ + -x "test --workspace --all-features" +fi + +echo "Install Bacon with: cargo install --locked bacon" >&2 +exit 127 diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md new file mode 100644 index 0000000..c00e2b4 --- /dev/null +++ b/tests/fixtures/README.md @@ -0,0 +1,18 @@ +# Deterministic fixtures + +Fixtures in this directory are synthetic and safe to publish. They must never be +copied from real subscription transcripts, private repositories, shell history, +or credentials. + +Planned fixture families: + +- `harness/`: scripted child processes covering stdout, stderr, exit, signals, + prompts, long lines, invalid UTF-8, backpressure, and process-tree cleanup. +- `acp/`: versioned initialize/session/prompt/cancel transcripts and capability + combinations, including malformed and out-of-order frames. +- `terminal/`: compact VT byte streams with expected screen and scrollback state. +- `usage/`: provider-, harness-, measured-, and estimated-usage observations. +- `recovery/`: event logs ending at every externally visible interruption point. + +Large generated corpora belong in release artifacts or a dedicated test-data +repository, not in normal Git history.