Add --interactive Textual TUI for browse + build
## Summary
Adds an interactive TUI launched via `mpbuild --interactive` (or `-i`).
**Layout** (per the plan):
```
┌──────────────────────────────────────────────────────────┐
│ Header │
├──────────────┬───────────────────────────────────────────┤
│ │ #info-pane │
│ #board-tree │ Vendor / Product / MCU / Port / URL │
│ (Tree of │ Variant ▼ (when board has variants) │
│ ports → │ ▕ Build ▏ ▕ Clean ▏ │
│ boards) ├───────────────────────────────────────────┤
│ │ #build-log (RichLog, streaming output) │
├──────────────┴───────────────────────────────────────────┤
│ Footer — q · b · c │
└──────────────────────────────────────────────────────────┘
```
**Behaviour**
- Tree populated from the existing `Database` (ports → boards), sorted.
- Selecting a leaf populates the info pane, enables Build/Clean, and reveals/hides the variant `Select` based on whether the board has variants.
- Build/Clean both run via `docker_build_cmd()` reused verbatim, then a small `_stream_command` helper that runs the result in a `subprocess.Popen` line-buffered and yields stdout/stderr lines. A Textual `@work(thread=True, exclusive=True, group="build")` worker pushes each line into a `RichLog` via `call_from_thread` — clicking Build twice in a row cancels the in-flight build.
- `docker_interactive=False` is passed to `docker_build_cmd` so docker doesn't try to re-attach `-it` while the TUI owns the terminal.
**CLI integration**
`--interactive` is added to the existing `@app.callback()` with `is_eager=True`, mirroring `--version`. That sidesteps `chain=True`'s "missing command" complaint without restructuring the app. The `from .interactive import start_app` is lazy so non-TUI users don't pay the textual import cost on every CLI call.
**Tests** — 8 new (107 total)
`tests/test_interactive.py` (6, async, using `Pilot`):
- Tree populates with ports/boards from a synthesised `mpy_root`.
- Build/Clean/Variant Select all start disabled.
- Selecting a board fills the info pane (vendor, product, MCU) and enables Build/Clean.
- Variant Select is shown for boards with variants, hidden otherwise.
- Selecting a port (non-leaf) keeps actions disabled.
`tests/test_cli.py` (2 added): `--interactive` and `-i` both call `start_app` and exit 0 (with `start_app` monkeypatched).
**Not tested**: the actual subprocess invocation of docker. The streaming worker is an integration concern; `docker_build_cmd` is already well covered, and the worker is a thin stitch between it and `RichLog`.
## Prior work acknowledged
The abandoned `origin/interactive-mode` branch had a partial Textual app (`90535ec`). I lifted structural ideas (tree population, compose shape) but rewrote against current main — that branch references `typing_extensions` (removed in #96), a renamed `IDF_DEFAULT` constant, and stops short of actually invoking a build.
## Test plan
- [x] `uv run pytest -v` — 107 passed.
- [x] `uv run ruff check && uv run ruff format --check` — green.
- [x] `uv run ty check` — `All checks passed!`
- [x] `uv run pre-commit run --all-files` — all hooks green (incl. local `ty` hook).
- [x] `uv build --wheel` — wheel includes `mpbuild/interactive.tcss`.
- [ ] Manual smoke from inside a MicroPython tree (next time I'm at the bench): tree populates, selecting boards updates the info pane, Build streams docker output to the log, q quits.
合并状态:未合并 关闭于 2026-04-26 1 条评论