@ashley-shrok/viewmodel-shell 0.4.9 → 0.6.0
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 +26 -6
- package/dist/browser.d.ts +4 -0
- package/dist/browser.js +20 -0
- package/dist/index.d.ts +31 -1
- package/dist/index.js +99 -3
- package/dist/server.d.ts +7 -0
- package/dist/server.js +7 -0
- package/dist/tui-cli.js +85 -98
- package/dist/tui.d.ts +65 -141
- package/dist/tui.js +1064 -1535
- package/package.json +9 -11
package/dist/tui.d.ts
CHANGED
|
@@ -1,151 +1,75 @@
|
|
|
1
|
-
import type { ReactElement } from "react";
|
|
2
1
|
import type { Adapter, ActionEvent, ViewNode } from "./index.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* invokes `onSpawnError`. Callers map BOTH false and onSpawnError to the loud
|
|
13
|
-
* interstitial, so a redirect is never silently lost. Exported so a unit test
|
|
14
|
-
* can drive it with a stubbed node:child_process. */
|
|
15
|
-
export declare function openExternal(url: string, onSpawnError?: () => void): boolean;
|
|
16
|
-
/** How the vms-tui CLI's default onRedirect classifies a server redirect.
|
|
17
|
-
* No new wire — pure URL/origin analysis against the current endpoint. A
|
|
18
|
-
* relative path resolves against the endpoint ⇒ same-origin by construction;
|
|
19
|
-
* an absolute URL is same-origin iff its `origin` matches (origin compare,
|
|
20
|
-
* never a string prefix — `http://evil/?x=http://good` must NOT pass). */
|
|
21
|
-
export type RedirectKind = {
|
|
22
|
-
kind: "same-origin";
|
|
23
|
-
endpoint: string;
|
|
24
|
-
} | {
|
|
25
|
-
kind: "different-origin";
|
|
26
|
-
url: string;
|
|
27
|
-
} | {
|
|
28
|
-
kind: "invalid";
|
|
29
|
-
url: string;
|
|
30
|
-
};
|
|
31
|
-
export declare function classify(url: string, fromEndpoint: string): RedirectKind;
|
|
32
|
-
/**
|
|
33
|
-
* Phase 5b terminal adapter — single-line + multi-line editors + selects.
|
|
34
|
-
*
|
|
35
|
-
* Every node renders byte-identically to Phase 1/2 when UNFOCUSED and on the
|
|
36
|
-
* pure `renderTree`/non-TTY path (NO_CTX → interactive:false → no editor). On
|
|
37
|
-
* a TTY the shell's view is interactive: a self-managed focus ring
|
|
38
|
-
* (Tab/Shift-Tab/arrows), Enter/Space dispatch, focus continuity; button /
|
|
39
|
-
* checkbox (`{checked}`) / tabs (`{value}`) / link / copy-button (OSC 52) /
|
|
40
|
-
* form-submit. The focused single-line `field` is an editable
|
|
41
|
-
* `ink-text-input`; the focused `textarea`/`code` field is the contained
|
|
42
|
-
* `MultilineEditor` (Enter→newline; submit via the ring's submit button;
|
|
43
|
-
* `code` adds a dim language label, no literal-tab). The focused `select` is
|
|
44
|
-
* an `ink-select-input` list; the focused `select-multiple` is the contained
|
|
45
|
-
* `MultiSelectInput` (Space toggles; comma-joined wire value; submit via the
|
|
46
|
-
* ring's submit button). Typed drafts survive poll/dispatch re-renders unless
|
|
47
|
-
* the field disappears or the server changes its value — mirroring
|
|
48
|
-
* BrowserAdapter; selects are additionally server-authoritative on ANY server
|
|
49
|
-
* re-render (excluded from draft preservation); password is masked;
|
|
50
|
-
* form-`checkbox` toggles on Space. The still-deferred tier (`file`/`modal`/
|
|
51
|
-
* `table`) arrives in later phases and still renders a visible fail-loud
|
|
52
|
-
* placeholder — never blank, never silent.
|
|
53
|
-
*
|
|
54
|
-
* LEAF MODULE: never imported by src/index.ts / src/browser.ts /
|
|
55
|
-
* src/server.ts, so `ink`/`react` never enter the web or server dependency
|
|
56
|
-
* graph (a unit test asserts this).
|
|
57
|
-
*/
|
|
2
|
+
interface TuiOpts {
|
|
3
|
+
/** "fill" (default) takes the full terminal via alternate-screen buffer;
|
|
4
|
+
* "content" renders intrinsic content size in the inline scrollback. */
|
|
5
|
+
viewport?: "fill" | "content";
|
|
6
|
+
/** Sidebar rail fraction (0.15–0.6; default 1/3). When `page.layout === "sidebar"`
|
|
7
|
+
* (and the same on `section.layout`), the FIRST child occupies this fraction
|
|
8
|
+
* of the available width; the remainder fills with the rest of the children. */
|
|
9
|
+
sidebarFraction?: number;
|
|
10
|
+
}
|
|
58
11
|
export declare class TuiAdapter implements Adapter {
|
|
59
|
-
private
|
|
12
|
+
private renderer;
|
|
13
|
+
private root;
|
|
14
|
+
private pending;
|
|
15
|
+
private initPromise;
|
|
60
16
|
private disposed;
|
|
61
|
-
/** "fill" (default) = occupy the terminal + alternate-screen on a real
|
|
62
|
-
* interactive TTY; "content" = legacy intrinsic-content size, no
|
|
63
|
-
* alt-screen (the opt-out escape hatch — pre-0.4.5 behavior). */
|
|
64
17
|
private readonly viewport;
|
|
65
|
-
/** `sidebar` rail width as a fraction of the terminal (fill path only).
|
|
66
|
-
* Default 1/3; clamped to a sane [0.15, 0.6] so a bad value can't break
|
|
67
|
-
* the layout. Adapter-level styling knob (NOT wire — appearance, not
|
|
68
|
-
* arrangement). */
|
|
69
18
|
private readonly sidebarFraction;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
private
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
*
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
private lastVm;
|
|
91
|
-
private lastOnAction;
|
|
92
|
-
setRequestExit(fn: (code: number) => void): void;
|
|
19
|
+
private readonly sessionStore;
|
|
20
|
+
private focusedPaneIndex;
|
|
21
|
+
private lastPaneCount;
|
|
22
|
+
private readonly fieldValues;
|
|
23
|
+
private readonly fieldWireValues;
|
|
24
|
+
private copiedKey;
|
|
25
|
+
private copiedTimer;
|
|
26
|
+
constructor(opts?: TuiOpts);
|
|
27
|
+
private setFieldValue;
|
|
28
|
+
/** Resolve a field's display value at render time. The draft-preservation
|
|
29
|
+
* contract (AGENTS.md) is "user edits survive re-renders UNLESS the server
|
|
30
|
+
* explicitly sets a new value for that field." Three states:
|
|
31
|
+
* 1. First time we see this field name — initialize the wire baseline
|
|
32
|
+
* WITHOUT clobbering any pre-existing edit value (matters when a test
|
|
33
|
+
* primes fieldValues via setFieldValue before the first render).
|
|
34
|
+
* 2. Wire value unchanged since last render — preserve the edit value.
|
|
35
|
+
* 3. Wire value differs from the prior wire baseline — server intent
|
|
36
|
+
* change → reset the edit value to match. */
|
|
37
|
+
private copy;
|
|
38
|
+
private resolveFieldValue;
|
|
93
39
|
render(vm: ViewNode, onAction: (action: ActionEvent) => void): void;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
*
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
40
|
+
private init;
|
|
41
|
+
private flushPending;
|
|
42
|
+
private navigateForLinks;
|
|
43
|
+
/** Test-only: read the current edit value for a named field (for asserting
|
|
44
|
+
* draft preservation across server re-renders). */
|
|
45
|
+
_peekFieldValue(name: string): string | undefined;
|
|
46
|
+
/** Test-only: read the current "copied" key for asserting that activation
|
|
47
|
+
* set the feedback state. Returns null when no button is currently flashing. */
|
|
48
|
+
_peekCopiedKey(): string | null;
|
|
49
|
+
private cycleFocus;
|
|
50
|
+
/** B5 — keyboard activation of the focused pane's primary actionable.
|
|
51
|
+
*
|
|
52
|
+
* Mode mapping:
|
|
53
|
+
* "enter" → dispatch the first button.action / link → navigate /
|
|
54
|
+
* copy-button → copy(). No-op if the pane has inputs
|
|
55
|
+
* (FieldView's <input onSubmit> handles Enter for forms).
|
|
56
|
+
* "space" → toggle the first checkbox (dispatch action with
|
|
57
|
+
* {checked: !node.checked}). No-op if the pane has inputs
|
|
58
|
+
* (Space is a printable character in text fields).
|
|
59
|
+
*
|
|
60
|
+
* Always reads pane state from the CURRENT vm (this.pending), so a key
|
|
61
|
+
* pressed mid-render still acts on the structure the user can see.
|
|
62
|
+
*/
|
|
63
|
+
private activatePane;
|
|
64
|
+
/** Teardown — restore terminal cleanly. Idempotent. */
|
|
65
|
+
dispose(): void;
|
|
112
66
|
navigate(url: string): void;
|
|
113
|
-
/** Write-only client storage. `session` → in-memory (process lifetime).
|
|
114
|
-
* `local` → a SYNCHRONOUS XDG state-file write — synchronous is mandatory:
|
|
115
|
-
* the core applies side-effects before the redirect branch (adapter-seam
|
|
116
|
-
* Case E), so the token must land before navigation. Fail-loud, never
|
|
117
|
-
* swallow, never re-throw into core (push() has no try/catch): an I/O error
|
|
118
|
-
* or an unparseable EXISTING store surfaces the loud interstitial + a
|
|
119
|
-
* stderr line and does NOT clobber a possibly-unrelated user file. */
|
|
120
67
|
storage(scope: "local" | "session", key: string, value: string): void;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
*
|
|
68
|
+
saveFile(data: Blob, filename: string, _contentType: string): Promise<void>;
|
|
69
|
+
/** Test-only: read back a session value. The wire contract has no
|
|
70
|
+
* storage read; this exists solely so unit tests can prove the write
|
|
71
|
+
* landed (parity with the Ink adapter's _peekSession). */
|
|
124
72
|
_peekSession(key: string): string | undefined;
|
|
125
|
-
/** Pure ViewNode → Ink element, UNFOCUSED (NO_CTX). Used by static unit
|
|
126
|
-
* tests; byte-identical to Phase 1 output. */
|
|
127
|
-
renderTree(vm: ViewNode): ReactElement;
|
|
128
|
-
/** Spacing rhythm. compact collapses gaps/padding (mirrors .vms--compact). */
|
|
129
|
-
private spacing;
|
|
130
|
-
/**
|
|
131
|
-
* React key: the roadmap's identity heuristic — explicit id/name where
|
|
132
|
-
* present, else positional index. Render-only; never a wire field.
|
|
133
|
-
*/
|
|
134
|
-
private keyOf;
|
|
135
|
-
/** Fail-loud placeholder — single source of the phase string. */
|
|
136
|
-
private unsupported;
|
|
137
|
-
/** Focused-node affordance. Unfocused → returns the element unchanged, so
|
|
138
|
-
* the static path stays byte-identical to Phase 1. Focused → a leading
|
|
139
|
-
* cyan caret (deterministic + information-honest; ▸ U+25B8 is distinct
|
|
140
|
-
* from the list-item markers › / ·). */
|
|
141
|
-
private focusWrap;
|
|
142
|
-
/** Shipped list-item variant → marker + child tint (fail-soft: unknown ok). */
|
|
143
|
-
private listItemStyle;
|
|
144
|
-
/** page & section share the 4 layout presets. Information-honest, not pixel. */
|
|
145
|
-
private layoutContainer;
|
|
146
|
-
private renderNode;
|
|
147
|
-
/** Resolves when the Ink app unmounts; immediate if nothing was rendered. */
|
|
148
|
-
waitUntilExit(): Promise<void>;
|
|
149
|
-
/** Idempotent terminal restore — unmount restores cursor/raw mode. */
|
|
150
|
-
dispose(): void;
|
|
151
73
|
}
|
|
74
|
+
export declare function renderTree(vm: ViewNode): React.ReactNode;
|
|
75
|
+
export {};
|