Reuse canonical escaped absolute reference labels; retain structured operation selection across loading and row movement. Freeze editor targets and retire confirmation on Client observation invalidation. Mirror retained stale/loading Object, tree and Interface state without ref fabrication or automatic execution. Validation: 36 TUI unit tests, real filesystem HTTP workflow, 8 formatter and 6 asynchronous runtime tests, and 2 doc-tests pass. Full workspace fmt/check/all-target tests/doc-tests, Nix clippy -D warnings, rustdoc -D warnings, public example and diff check pass. Scoped regressions cover same-name dispatch, stale/ref-less/replaced/deleted scopes, old in-flight contexts and selective typed-entry observation.
220 lines
11 KiB
Markdown
220 lines
11 KiB
Markdown
# 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
|
||
|
||
Supply one `http` or `https` endpoint:
|
||
|
||
```console
|
||
cargo run -p wip-tui -- --endpoint https://wip.example.test/api/wip
|
||
```
|
||
|
||
The same canonical endpoint is used unchanged to construct requests, perform
|
||
HTTP I/O, and partition the client cache. WIP over HTTP does not define separate
|
||
HTTPS and development-HTTP profiles; URL scheme, TLS, certificate, authentication,
|
||
and proxy policy belong to the HTTP implementation and deployment.
|
||
|
||
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.
|
||
|
||
### 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 \
|
||
--read-write
|
||
```
|
||
|
||
In another:
|
||
|
||
```console
|
||
cargo run -p wip-tui -- http://127.0.0.1:8080/wip
|
||
```
|
||
|
||
Expand `/`, select `notes.txt`, focus the Interface/Operation pane, and choose
|
||
`read`; descriptors advertised by visible Objects are normally prefetched already.
|
||
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.
|
||
|
||
## Scoped reference formatting
|
||
|
||
`wip_tui::reference::{format_interface_reference, format_qualified_operation}`
|
||
provides labels for the existing UI from `InterfaceReference` and a separate
|
||
operation name, following `4.2-text-view.md` reference notation (`6086f3c`).
|
||
|
||
```rust
|
||
use wip_protocol::InterfaceReference;
|
||
use wip_tui::reference::{format_interface_reference, format_qualified_operation};
|
||
|
||
let reference = InterfaceReference { scope: "/github".into(), name: "issue#read".into() };
|
||
assert_eq!(format_interface_reference(&reference)?, "/github::\"issue#read\"");
|
||
assert_eq!(format_qualified_operation(&reference, "read#raw")?,
|
||
"/github::\"issue#read\"#\"read#raw\"");
|
||
# Ok::<(), wip_protocol::ValidationError>(())
|
||
```
|
||
|
||
Both return `Result<String, ValidationError>` and reject invalid reference shape,
|
||
including relative/omitted scope. Root is explicit `/`. Non-simple components use
|
||
JSON string quoting with additional DEL/C1, Unicode line/paragraph separator and
|
||
bidi-control escapes. Input values are borrowed and never normalized, case-folded,
|
||
URL-encoded or split on delimiters. Ref, validators and `scope_ref` do not enter
|
||
these labels. The output has no extra outer signature string quoting.
|
||
|
||
Keep selection, cache and calls keyed by structured references and operation
|
||
names, never display text. No parser, scope inference, identity reconstruction,
|
||
Object/Interface signature renderer or new crate is introduced. Membership,
|
||
ancestry, authorization and freshness remain runtime concerns. Normal Entry,
|
||
Interface/Operation, editor, confirmation and operation-history screens use these
|
||
qualified labels with existing bounded layout. Shortened labels never feed back
|
||
into identity or calls.
|
||
|
||
### Rust API and wire migration
|
||
|
||
Interface identities in the presentation map, action/selection rows, in-flight
|
||
subjects and editors are now `InterfaceReference { scope, name }`, not strings.
|
||
This is a breaking Rust API change; consumers must construct an explicit canonical
|
||
absolute scope rather than parse a label or infer a scope from a target Entry.
|
||
`ArgumentEditor` also retains its Entry `path`; `new` defaults to `/`, and
|
||
`for_target(path)` fixes the chosen target before editing. Normal UI selection
|
||
sets it automatically. Confirmation never retargets an existing editor.
|
||
`App::operation_selection` retains the Entry and structured row through temporary
|
||
loading gaps; the numeric cursor is only a layout position. Retaining this subject
|
||
does not expose stale operations or authorize execution.
|
||
|
||
The TUI uses the scoped Protocol/HTTP Client wire: Interface references are
|
||
structured, and calls use an Interface target carrying the reference and optional
|
||
`scope_ref`/validator preconditions. The optional identity is copied by the Client,
|
||
not invented by the UI; no additional required ref is introduced. This wire is not
|
||
compatible with the former string-reference request/response shape.
|
||
|
||
## 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 |
|
||
| `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 `/`, lazily materializes expanded nodes, and
|
||
prefetches children for each visible Object so emptiness and the next expansion
|
||
frontier are known without selecting that Object first. Text labels distinguish
|
||
unloaded, loading, empty, stale, and failed state; color is supplemental. Every
|
||
successful expansion is complete for its requested depth.
|
||
- **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 structured `(scope,
|
||
interface name, operation name)`, displaying `scope::interface#operation` with
|
||
explicit absolute scope and safely quoted components. Structured references
|
||
remain intact in selection, acquisition, and call state; display labels are
|
||
never parsed back into references. Descriptor lookahead follows fresh visible
|
||
Objects, shares the
|
||
client's in-flight/cache bounds, and never retries retained failures implicitly.
|
||
The pane 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 may update the
|
||
cached Object validator but does not by itself make the Object observation stale.
|
||
Validator mismatch, interface mismatch, `OperationOutcomeUnknown`, undecodable
|
||
post-dispatch responses, and post-dispatch timeout/disconnect are displayed using
|
||
the runtime's authoritative transition.
|
||
|
||
Every Client transition is mirrored into retained UI Object, tree and Interface
|
||
state, including scope retirement and cache removal. An open editor/confirmation
|
||
is bound to private Object/Interface snapshots (not displayed refs/validators).
|
||
Changed, stale, loading, removed or retargeted observations retire the editor;
|
||
reobservation never recreates or executes it. Descriptor rows remain identified
|
||
by the structured reference when another Interface gains or loses operation rows.
|
||
Explicitly select/load an Interface header to refresh a retained stale descriptor,
|
||
then select an operation and confirm a new call. Client calls already dispatched
|
||
retain their original immutable context and are never moved to a new scope.
|
||
|
||
Without refs, a same-path replacement with indistinguishable public metadata
|
||
cannot always be detected; the UI does not synthesize identity or infer deletion
|
||
from missing tree edges. Optional refs remain optional. Typed `Entry` results are
|
||
only observed when individually selected; the UI neither bulk-fetches result
|
||
paths nor adds guessed parent/indexable edges.
|
||
|
||
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.
|