feat: add interactive WIP terminal client

This commit is contained in:
2026-09-30 10:35:05 +09:00
parent 347b92b28a
commit db2a51c824
14 changed files with 5141 additions and 13 deletions
+151
View File
@@ -0,0 +1,151 @@
# wip-tui
`wip-tui` is an interactive terminal client for exploring and operating a WIP
Worldspace. It is a presentation and terminal-interaction layer over
`wip-client`: Known Space, request identity, validators, immutable operation call
contexts, stale transitions, response classification, and bounded histories remain
owned by the client runtime.
## Start
HTTPS is the normal mode:
```console
cargo run -p wip-tui -- --endpoint https://wip.example.test/api/wip
```
The endpoint may instead be supplied as `WIP_ENDPOINT`. A non-secret cache
partition identity may be supplied with `--security-context NAME` or
`WIP_SECURITY_CONTEXT`. The default request timeout is 30 seconds and can be
changed with `--timeout SECONDS`.
An Authorization header can be read from an environment variable without placing
its value in arguments, application state, errors, snapshots, or debug output:
```console
export WIP_AUTHORIZATION='Bearer …'
cargo run -p wip-tui -- https://wip.example.test/wip
```
Use `--authorization-env OTHER_VARIABLE` to select another variable. The value is
redacted from `Debug` and is never rendered. Shell and process environment security
remain the operator's responsibility.
Plain HTTP is rejected by default. It is available only as an explicit loopback
development mode:
```console
cargo run -p wip-tui -- http://127.0.0.1:8080/wip --allow-http
```
Non-loopback HTTP remains rejected even with `--allow-http`. For production, use a
real HTTPS endpoint and a trusted TLS deployment.
### Filesystem server example
In one terminal:
```console
mkdir -p /tmp/wip-example
printf 'hello world' >/tmp/wip-example/notes.txt
cargo run -p wip-fs-server -- \
--root /tmp/wip-example \
--listen 127.0.0.1:8080 \
--base-path /wip \
--development-http \
--read-write
```
In another:
```console
cargo run -p wip-tui -- http://127.0.0.1:8080/wip --allow-http
```
Expand `/`, select `notes.txt`, focus the Interface/Operation pane, load the read
interface, and choose `read`. The editor labels `offset` and `length` as optional;
leave both blank to read the bounded file. `Bytes` results are shown as a bounded
hex preview. For `replace`, enter `contents` as RFC 4648 base64—for example,
`dXBkYXRlZA==` for `updated`—then review the target Entry, interface reference,
operation, and value on the confirmation screen before pressing Enter again.
## Keyboard bindings
All required actions are keyboard-accessible.
| Key | Action |
| --- | --- |
| `↑`/`k`, `↓`/`j` | Move within the focused tree, operation list, history, or editor |
| `→`/`l`, `Enter` | Expand a tree node lazily; load/select an interface or operation |
| `←`/`h` | Collapse a node or move to its parent |
| `Tab` | Cycle Tree → Selected Entry → Operations → History |
| `r` | Reobserve the selected Entry and its direct subtree |
| `+` | Double the selected node limit and retry an incomplete/truncated tree |
| `c` | Cancel the newest retrieval; a dispatched operation is never presented as canceled |
| `R` | Reconnect after connection failure |
| `?` | Open help |
| `Esc` | Close a modal or return to the tree |
| `q`, `Ctrl-C` | Exit and restore the terminal |
Inside an argument editor, printable keys are field input, arrows move fields,
Backspace edits, Enter validates, and Esc cancels. The confirmation screen requires
a second explicit Enter (or `e`) to execute once. There is no operation auto-retry.
## Screen model
- **Worldspace tree** starts at `/` and materializes direct children only when a
node is expanded. Text labels distinguish unloaded, loading, empty, stale,
failed, and truncated state; color is supplemental. A truncated node is never
presented as a complete tree.
- **Selected Entry** shows only public WIP path, object name, description, ordered
interfaces, and freshness. Validators, credentials, Host internals, and backend
paths are deliberately hidden. If current metadata disappears, old text is not
presented as current.
- **Interfaces / Operations** identifies every operation by `(interface reference,
operation name)`. It shows descriptor documentation, ordered parameters,
required/optional status, and result types.
- **Argument editor** supports Unit, Boolean, Integer, Number, String, base64 Bytes,
Record, List, Entry, Enum, Union, Json, and descriptor-local named types. Record,
List, Union, and Json values use JSON syntax. Union input is
`{"$case":"name","value":…}` when the selected case has a payload. Diagnostics
remain attached to the failing field.
- **Recent results / diagnostics** explicitly distinguishes success, Host failure,
invalid protocol response, transport failure, client-local failure, and unknown
operation outcome. Large values, bytes, nesting, and control characters are
bounded before rendering. Only descriptor-declared `Entry` results become
navigation targets; path-looking String or Json values do not.
Terminals below 48×14 show a minimum-size screen instead of attempting the full
layout.
## Validator and failure behavior
Object and interface validators are observed and attached automatically by
`wip-client`; users neither copy nor edit them. A successful operation conservatively
marks related object observations stale because metadata may have changed.
Validator mismatch, interface mismatch, `OperationOutcomeUnknown`, undecodable
post-dispatch responses, and post-dispatch timeout/disconnect are displayed using
the runtime's authoritative transition.
For an unknown outcome, the UI states that execution or side effects may have
occurred, marks the related Entry stale, and tells the user to refresh. Refreshing
does not recreate the call: the user must select the operation, review fresh input,
and explicitly execute again. The same no-auto-retry rule applies after validator
or interface mismatch.
Retrieval work runs on I/O workers while the input/render loop continues. Responses
return with the original `RequestId`, so an old completion cannot populate another
Entry's details. Canceling a retrieval records a local cancellation and drops its
late response. Once an operation crosses dispatch, cancellation is never described
as undoing execution.
## Tests
The library form keeps reducers and rendering independent of a real terminal.
`ratatui::TestBackend` snapshots cover tree/detail/result states and the minimum-size
fallback. Unit tests cover reducer focus, selection, modal confirmation, key
translation, descriptor inputs, bounded values, and descriptor-directed Entry
results. The real HTTP integration test launches `wip-fs-server` on a temporary
root and covers tree selection, reads, validator-protected replacement, external
stale-validator rejection without retry, explicit refresh/re-execution, and
symlink confinement.