@ashley-shrok/viewmodel-shell 0.4.3 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/tui-cli.js +17 -8
- package/dist/tui.js +10 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,7 +56,13 @@ const shell = new ViewModelShell({
|
|
|
56
56
|
shell.load();
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
The TUI is built on [Ink](https://github.com/vadimdemedes/ink), declared as
|
|
59
|
+
The TUI is built on [Ink](https://github.com/vadimdemedes/ink) and friends, declared as **optional** dependencies (`ink`, `react`, `ink-text-input`, `ink-select-input`) so web and server consumers are unaffected — they are never imported by the browser, server, or core entrypoints. `npx vms-tui` installs them automatically. **Project consumers using `TuiAdapter` programmatically must add all four explicitly** — optional dependencies are *not* pulled transitively when another project depends on this package (notably with `bun install`):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm i @ashley-shrok/viewmodel-shell ink react ink-text-input ink-select-input
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
See [AGENTS.md](https://github.com/ashley-shrok/ViewModelShell/blob/main/AGENTS.md) for what the terminal renders.
|
|
60
66
|
|
|
61
67
|
## Themes
|
|
62
68
|
|
package/dist/tui-cli.js
CHANGED
|
@@ -40,8 +40,9 @@ async function main() {
|
|
|
40
40
|
({ TuiAdapter, openExternal, classify } = await import("./tui.js"));
|
|
41
41
|
}
|
|
42
42
|
catch (err) {
|
|
43
|
-
process.stderr.write("vms-tui requires
|
|
44
|
-
"
|
|
43
|
+
process.stderr.write("vms-tui requires its optional peer packages: 'ink', 'react', " +
|
|
44
|
+
"'ink-text-input', 'ink-select-input'.\n" +
|
|
45
|
+
"Install them: npm i ink react ink-text-input ink-select-input\n" +
|
|
45
46
|
`(load error: ${err.message})\n`);
|
|
46
47
|
process.exitCode = 1;
|
|
47
48
|
return;
|
|
@@ -119,6 +120,14 @@ async function main() {
|
|
|
119
120
|
// onRedirect, so redirects chain.
|
|
120
121
|
let redirectFailed = false;
|
|
121
122
|
let currentShell;
|
|
123
|
+
// Non-interactive iff EITHER stream is not a TTY. Checking stdin too (not
|
|
124
|
+
// just stdout) is load-bearing: with the tui.tsx isActive fix the App's
|
|
125
|
+
// useInput is correctly inert on non-TTY stdin, so nothing holds the event
|
|
126
|
+
// loop — if stdout were a TTY but stdin a pipe (`vms-tui url </dev/null`
|
|
127
|
+
// from an interactive shell), a stdout-only guard would fall through to the
|
|
128
|
+
// keep-alive + waitUntilExit() and HANG forever (no input can ever arrive).
|
|
129
|
+
// One static frame + exit is the only correct behavior when stdin is dead.
|
|
130
|
+
const nonInteractive = !process.stdout.isTTY || !process.stdin.isTTY;
|
|
122
131
|
const handleRedirect = (url, fromEndpoint) => {
|
|
123
132
|
const c = classify(url, fromEndpoint);
|
|
124
133
|
if (c.kind === "same-origin") {
|
|
@@ -127,9 +136,9 @@ async function main() {
|
|
|
127
136
|
void currentShell.load(); // failures flow through the shared onError
|
|
128
137
|
return;
|
|
129
138
|
}
|
|
130
|
-
if (
|
|
131
|
-
// Non-
|
|
132
|
-
// SINGLE shutdown funnel (redirectFailed is read at the exit site).
|
|
139
|
+
if (nonInteractive) {
|
|
140
|
+
// Non-interactive: never spawn a browser; loud stderr + nonzero exit via
|
|
141
|
+
// the SINGLE shutdown funnel (redirectFailed is read at the exit site).
|
|
133
142
|
process.stderr.write(`vms-tui: cannot follow redirect (${c.kind}): ${url}\n`);
|
|
134
143
|
redirectFailed = true;
|
|
135
144
|
return;
|
|
@@ -167,9 +176,9 @@ async function main() {
|
|
|
167
176
|
loadFailed = true;
|
|
168
177
|
process.stderr.write(`vms-tui: ${err.message}\n`);
|
|
169
178
|
}
|
|
170
|
-
if (
|
|
171
|
-
// Non-
|
|
172
|
-
// and exit instead of hanging
|
|
179
|
+
if (nonInteractive) {
|
|
180
|
+
// Non-interactive (piped / CI / dead stdin): nothing to interact with —
|
|
181
|
+
// emit one static frame and exit instead of hanging forever. The delay
|
|
173
182
|
// lets Ink flush its throttled render. (Redirects can't fire here: load()
|
|
174
183
|
// ignores response.redirect and non-TTY performs no dispatch — so
|
|
175
184
|
// redirectFailed stays false unless a future code path dispatches.)
|
package/dist/tui.js
CHANGED
|
@@ -616,7 +616,16 @@ function App(props) {
|
|
|
616
616
|
const { vm, renderWith } = props;
|
|
617
617
|
const { isRawModeSupported } = useStdin();
|
|
618
618
|
const { write } = useStdout();
|
|
619
|
-
|
|
619
|
+
// `=== true` is LOAD-BEARING, not defensive. Ink's App.isRawModeSupported()
|
|
620
|
+
// returns `this.props.stdin.isTTY`, which is `true` on a TTY but `undefined`
|
|
621
|
+
// (never `false`) on a non-TTY stdin (pipe / </dev/null / agent / CI). Ink's
|
|
622
|
+
// useInput skips raw mode ONLY when `options.isActive === false` (strict).
|
|
623
|
+
// So passing the raw `undefined` as isActive does NOT skip → Ink calls
|
|
624
|
+
// setRawMode → throws "Raw mode is not supported" and dumps a react error
|
|
625
|
+
// frame on the non-TTY path. Coercing to a real boolean makes the gate
|
|
626
|
+
// `{ isActive: false }`, which Ink honors → clean static render. (A TTY
|
|
627
|
+
// gives `true`; ink-testing-library gives `true` → tests unchanged.)
|
|
628
|
+
const interactive = isRawModeSupported === true;
|
|
620
629
|
const [focusedKey, setFocusedKey] = useState(null);
|
|
621
630
|
const [copiedKey, setCopiedKey] = useState(null);
|
|
622
631
|
// User-typed field drafts, keyed by focus key. Mirrors BrowserAdapter's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashley-shrok/viewmodel-shell",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "A server-driven UI framework where the wire format is structured enough that agents can build full-stack apps without ever opening a browser and all UI tests are pure unit tests with no browser runtime. Server returns a JSON tree of typed nodes; a thin TypeScript adapter renders it to DOM. Backend-agnostic — a .NET reference backend ships with the repo, but any language can produce the JSON contract.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|