edamame 0.1.5

A fast TUI Markdown editor and viewer
Documentation
# Windows: support stance and status

Windows is a **best-effort** platform until a Windows contributor shows up. This file is the source of truth for what is verified, what is not, and how. The user-facing statements are the README's *Platforms* section and [Windows and WSL](../terminal-compatibility.md#windows-and-wsl); keep them consistent with this file when the stance changes.

## The stance

- edamame **must compile, lint, and pass its test suite** on `x86_64-pc-windows-msvc`. CI enforces that on every push and pull request, and a red Windows run blocks a merge like any other. "Best-effort" describes what we *verify*, not what we tolerate in CI. If a Linux-only contributor is stalled by a Windows-only failure, the escape hatch is a `#[cfg(not(windows))]` on the test with a comment saying why — never `continue-on-error`, which reports a red check on every PR even though the workflow passes.
- **Nobody runs the program on Windows.** Terminal behavior — ConPTY setup and restore, Windows Terminal vs. conhost, image protocols, the OS clipboard, `start`-based link opening, the `$EDITOR`-less external-editor path, `git difftool` walks — is unverified, and the user docs say so. Windows bug reports are triaged like any other.
- **No Windows binary is shipped.** `dist-workspace.toml` has no Windows target; users build from source. Shipping one is a separate decision from supporting the platform, revisited when someone can actually run what comes out.
- **WSL is Linux.** It runs the Linux build and is covered by the Linux job. Its own limitations (documented for users in [terminal-compatibility.md](../terminal-compatibility.md#windows-and-wsl)) come from WSL itself and are not tracked as edamame bugs.

Interactive testing without a Windows machine — a local KVM VM with a Microsoft eval image, or an Actions runner with an RDP tunnel — was deliberately deferred: it is the route to promoting Windows to supported, but not worth the recurring cost for a platform nobody on the project uses.

## What is verified, and how

### In CI: the `windows` job

`.github/workflows/ci.yml` has a separate `windows` job (not a third `os:` entry in the Unix matrix — its steps differ). It runs on `windows-latest`:

1. `cargo clippy --all-targets --all-features -- -D warnings`. **This is the only place `arboard`'s Windows clipboard backend is compiled at all.** The `clippy` job is ubuntu-only and every test job runs `--no-default-features`, so without this step that code is linted by nothing. The `--no-default-features` lint is skipped here; the ubuntu job already covers the platform-neutral code that configuration reaches.
2. `cargo test --no-fail-fast --no-default-features`. Same feature choice as the Unix jobs and for the same reason: the OS clipboard is process-global, and tests would race on it.
3. The two `--ignored` watcher invocations, exercising `notify`'s `ReadDirectoryChangesW` backend — the only coverage that backend has. If it proves flaky under the runner's filesystem, gate *that step*, not the job.

If the job's wall clock (about 2 minutes warm) grows to dominate the workflow, move the watcher step to a nightly `schedule:` first.

### Locally: the msvc cross-check from Linux

The msvc target can be type-checked and linted from Linux, though not run:

```bash
rustup target add x86_64-pc-windows-msvc && cargo install cargo-xwin   # once
cargo xwin clippy --target x86_64-pc-windows-msvc --all-targets --all-features -- -D warnings
cargo xwin clippy --target x86_64-pc-windows-msvc --all-targets --no-default-features -- -D warnings
```

Why `cargo-xwin` and when to run both passes are in [`AGENTS.md`](../../AGENTS.md#cross-checking-the-windows-build). Two additions: `xwin`'s first-use download (a few hundred MB) accepts Microsoft's SDK license on your behalf, and the `x86_64-pc-windows-gnu` target is deliberately not used — it would build without the SDK, but nobody on Windows uses it and its `cfg` surface is nearly identical, so it finds nothing new.

### Checkout: `.gitattributes`

`* text=auto eol=lf`. GitHub's Windows runner images set `core.autocrlf=true`, which without this line converts every LF file to CRLF at checkout — the `insta` snapshots, `tests/fixtures/*.md` (byte offsets feed the source map), and the `include_str!`d `config/config.toml` and `CHANGELOG.md`. (Observed: the one test reading an offset-sensitive fixture from disk failed on Windows only.) It also protects the CRLF-preservation feature's own tests, which construct their `\r\n` input in code and must not have the surrounding source rewritten.

## What is not verified

Every layer `cargo test` reaches through `TestBackend` — parser, renderer, editing model, diff, source map, modals — is platform-neutral and is proven on Windows by the job above. What remains unproven:

- Terminal setup and restore through ConPTY; `Picker::from_query_stdio` and the image protocols. Windows Terminal ≥ 1.22 has Sixel and no Kitty graphics, and its DA1 answer — sixel support — does survive ConPTY; conhost has neither an alternate screen nor mouse reporting worth relying on. A partly visible image takes the same sixel *band* path there as on the Kitty terminals, which is exactly the part nobody has watched on a real Windows Terminal.
- `open::that` (`cmd /C start`) for links and non-Markdown files.
- The external editor: `Command::new($EDITOR)` with the suspend/resume dance, and the `open::that` fallback when `$VISUAL` / `$EDITOR` are unset — which on Windows is the common case.
- `arboard` at runtime.
- `difftool::stop_walk`: there is no SIGINT, so it falls through to `process::exit(EXIT_INTERRUPTED)`, which stops a walk run with `--trust-exit-code` and nothing else. Whether Git for Windows hands `edamame --diff` a `/dev/null` or a `nul` for an absent side is also unknown; `diff_label` matches the former literally.
- **The custom-export runner has zero Windows test coverage.** All seven tests in `src/export/custom.rs` are `#[cfg(unix)]` because they shell out to `cp` / `cat` / `false`. Porting them (`cmd /C copy`, `cmd /C type`, `cmd /C exit 1`) is the first thing to hand a Windows contributor.

## Rules for tests, learned the hard way

The first full Windows run failed seven lib tests and, without `--no-fail-fast`, never built `tests/` at all — hence `--no-fail-fast` on every CI test step. The seven had three causes, each yielding a rule:

**A `/`-rooted literal is not an absolute path on Windows.** `Path::new("/tmp/notes.md").is_absolute()` is `false` — there is no drive letter — so code that calls `std::path::absolute` prepends the cwd and code that checks `is_absolute()` takes its fallback branch. Tests asserted correct behavior against a wrong fixture (`dirty_conflict::local_copy_path_*`, `save_copy_modal::save_as_default_*`, `export::custom::absolutize_*`, `config::config_dir_prefers_absolute_xdg_config_home`). The rule: **build paths from `tempfile::tempdir()` wherever a path reaches the filesystem or `absolute()`, and assert against `dir.path().join(..)`**; use a `cfg!(windows)`-selected literal (`C:\name` / `/name`, see `abs_root()` in `config::config::tests`) only for a pure function that never touches the disk. Never fix one of these with a `#[cfg(unix)]` gate — the point is that the behavior holds on Windows.

**A test of a Unix feature is Unix-only, and says so.** `difftool::read_side_reads_dev_null_as_an_empty_side` asserts on the OS's null device. It is `#[cfg(unix)]` with a comment, and an unconditional sibling (`read_side_reads_an_empty_file_as_an_empty_side`) covers the property everywhere. The rule: gate the *feature*, keep the *property* unconditional.

**An import that only gated tests use is an error on the other platform.** `export::custom`'s test module imported `std::sync::mpsc` for its seven `cfg(unix)` tests; on Windows that was an unused import and a hard error under `-D warnings`. Gate the import alongside the tests, with a comment. The local msvc cross-check catches this before CI does.

## Open items

- **Issue #2** ("Windows testing") — close with a pointer to this file once the stance is published.
- Port the seven `export::custom` tests — needs a Windows contributor, or the deferred VM.
- The `/dev/null` vs. `nul` question for `--diff` under Git for Windows — same.
- A Windows entry in `dist-workspace.toml` — only after someone has run a binary.