@sigrun/tui 0.4.1 → 0.5.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/CHANGELOG.md +10 -1
- package/dist/actions.d.ts +119 -0
- package/dist/actions.js +12 -0
- package/dist/api.d.ts +209 -0
- package/dist/api.js +282 -0
- package/dist/blocks/braille.d.ts +41 -0
- package/dist/blocks/braille.js +131 -0
- package/dist/blocks/chart.d.ts +20 -0
- package/dist/blocks/chart.js +397 -0
- package/dist/blocks/chat-transport.d.ts +44 -0
- package/dist/blocks/chat-transport.js +116 -0
- package/dist/blocks/collection/calendar.d.ts +28 -0
- package/dist/blocks/collection/calendar.js +232 -0
- package/dist/blocks/collection/card.d.ts +36 -0
- package/dist/blocks/collection/card.js +128 -0
- package/dist/blocks/collection/collection.d.ts +9 -0
- package/dist/blocks/collection/collection.js +26 -0
- package/dist/blocks/collection/grid.d.ts +18 -0
- package/dist/blocks/collection/grid.js +72 -0
- package/dist/blocks/collection/lanes.d.ts +32 -0
- package/dist/blocks/collection/lanes.js +320 -0
- package/dist/blocks/collection/list.d.ts +30 -0
- package/dist/blocks/collection/list.js +273 -0
- package/dist/blocks/collection/move.d.ts +67 -0
- package/dist/blocks/collection/move.js +96 -0
- package/dist/blocks/collection/shared.d.ts +87 -0
- package/dist/blocks/collection/shared.js +177 -0
- package/dist/blocks/collection/table.d.ts +26 -0
- package/dist/blocks/collection/table.js +221 -0
- package/dist/blocks/content.d.ts +48 -0
- package/dist/blocks/content.js +253 -0
- package/dist/blocks/context.d.ts +117 -0
- package/dist/blocks/context.js +65 -0
- package/dist/blocks/diff.d.ts +21 -0
- package/dist/blocks/diff.js +25 -0
- package/dist/blocks/ext-transport.d.ts +70 -0
- package/dist/blocks/ext-transport.js +165 -0
- package/dist/blocks/form/choice.d.ts +54 -0
- package/dist/blocks/form/choice.js +175 -0
- package/dist/blocks/form/fields.d.ts +56 -0
- package/dist/blocks/form/fields.js +136 -0
- package/dist/blocks/form/file.d.ts +61 -0
- package/dist/blocks/form/file.js +121 -0
- package/dist/blocks/form/form.d.ts +66 -0
- package/dist/blocks/form/form.js +764 -0
- package/dist/blocks/form/list.d.ts +37 -0
- package/dist/blocks/form/list.js +70 -0
- package/dist/blocks/form/values.d.ts +20 -0
- package/dist/blocks/form/values.js +106 -0
- package/dist/blocks/icons.d.ts +14 -0
- package/dist/blocks/icons.js +74 -0
- package/dist/blocks/image.d.ts +39 -0
- package/dist/blocks/image.js +82 -0
- package/dist/blocks/interaction-transport.d.ts +42 -0
- package/dist/blocks/interaction-transport.js +79 -0
- package/dist/blocks/layout.d.ts +31 -0
- package/dist/blocks/layout.js +155 -0
- package/dist/blocks/log.d.ts +24 -0
- package/dist/blocks/log.js +111 -0
- package/dist/blocks/meter.d.ts +16 -0
- package/dist/blocks/meter.js +50 -0
- package/dist/blocks/page.d.ts +54 -0
- package/dist/blocks/page.js +360 -0
- package/dist/blocks/render.d.ts +31 -0
- package/dist/blocks/render.js +163 -0
- package/dist/blocks/surface-view.d.ts +41 -0
- package/dist/blocks/surface-view.js +137 -0
- package/dist/blocks/surface.d.ts +80 -0
- package/dist/blocks/surface.js +506 -0
- package/dist/blocks/tones.d.ts +23 -0
- package/dist/blocks/tones.js +63 -0
- package/dist/blocks/treeview.d.ts +22 -0
- package/dist/blocks/treeview.js +121 -0
- package/dist/blocks/width.d.ts +32 -0
- package/dist/blocks/width.js +70 -0
- package/dist/clipboard.d.ts +20 -0
- package/dist/clipboard.js +19 -0
- package/dist/editor-launch.d.ts +34 -0
- package/dist/editor-launch.js +70 -0
- package/dist/keymaps/emacs.d.ts +18 -0
- package/dist/keymaps/emacs.js +172 -0
- package/dist/keymaps/standard.d.ts +17 -0
- package/dist/keymaps/standard.js +175 -0
- package/dist/keymaps/types.d.ts +43 -0
- package/dist/keymaps/types.js +28 -0
- package/dist/keymaps/vim.d.ts +48 -0
- package/dist/keymaps/vim.js +291 -0
- package/dist/launch.d.ts +159 -0
- package/dist/launch.js +478 -0
- package/dist/sse.d.ts +112 -0
- package/dist/sse.js +418 -0
- package/dist/theme.d.ts +51 -0
- package/dist/theme.js +119 -0
- package/dist/ui/ambient.d.ts +78 -0
- package/dist/ui/ambient.js +264 -0
- package/dist/ui/app.d.ts +319 -0
- package/dist/ui/app.js +1539 -0
- package/dist/ui/composer.d.ts +66 -0
- package/dist/ui/composer.js +133 -0
- package/dist/ui/conversation-pane.d.ts +143 -0
- package/dist/ui/conversation-pane.js +615 -0
- package/dist/ui/help.d.ts +15 -0
- package/dist/ui/help.js +80 -0
- package/dist/ui/interactions.d.ts +91 -0
- package/dist/ui/interactions.js +477 -0
- package/dist/ui/picker.d.ts +26 -0
- package/dist/ui/picker.js +130 -0
- package/dist/ui/settings.d.ts +68 -0
- package/dist/ui/settings.js +254 -0
- package/dist/ui/sidebar.d.ts +99 -0
- package/dist/ui/sidebar.js +269 -0
- package/dist/ui/status-bar.d.ts +77 -0
- package/dist/ui/status-bar.js +146 -0
- package/package.json +4 -3
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SurfaceView (UI-blocks U2 Task 5, the port of U1's `SurfaceView.tsx`):
|
|
3
|
+
* the component host for a `SurfaceState` — loading, not-found, an
|
|
4
|
+
* error line with `r` to retry, ready. Ready builds the render context
|
|
5
|
+
* (the confirmed-plus-overlay data, the `/view/` branch, server field
|
|
6
|
+
* errors, dispatch and access) and renders the tree's root block.
|
|
7
|
+
*
|
|
8
|
+
* `handleInput` forwards to the rendered root component while ready;
|
|
9
|
+
* while error is showing, `r` retries the load (spec §12.3's fallback
|
|
10
|
+
* posture: an error never strands the surface).
|
|
11
|
+
*/
|
|
12
|
+
import { renderBlock } from "./render.js";
|
|
13
|
+
import { truncate } from "./width.js";
|
|
14
|
+
/** The state lines (U1's wording, terminal-cased). */
|
|
15
|
+
export const SURFACE_LOADING = "loading…";
|
|
16
|
+
export const SURFACE_NOT_FOUND = "surface not found";
|
|
17
|
+
export const SURFACE_ERROR = "something went wrong — r to retry";
|
|
18
|
+
export class SurfaceView {
|
|
19
|
+
#surface;
|
|
20
|
+
#theme;
|
|
21
|
+
#locale;
|
|
22
|
+
#unicode;
|
|
23
|
+
#options;
|
|
24
|
+
#component;
|
|
25
|
+
/** The ONE mutable render context (fix round 2): the cached root
|
|
26
|
+
* component holds this object by reference, so a post-settle
|
|
27
|
+
* `setAccess("read")` reaches the form's `#readOnly` — refreshing
|
|
28
|
+
* `data`/`access` per render instead of rebuilding the component
|
|
29
|
+
* keeps typed state. */
|
|
30
|
+
#ctx;
|
|
31
|
+
constructor(surface, opts) {
|
|
32
|
+
this.#surface = surface;
|
|
33
|
+
this.#theme = opts.theme;
|
|
34
|
+
this.#locale = opts.locale ?? "en";
|
|
35
|
+
this.#unicode = opts.unicode;
|
|
36
|
+
this.#options = opts.options ?? {};
|
|
37
|
+
}
|
|
38
|
+
/** The render context, refreshed from `#surface.view` on every
|
|
39
|
+
* access (data updates and access swaps flow through to the
|
|
40
|
+
* cached component's `#readOnly`/binding reads). */
|
|
41
|
+
#context() {
|
|
42
|
+
const view = this.#surface.view;
|
|
43
|
+
if (this.#ctx === undefined) {
|
|
44
|
+
this.#ctx = {
|
|
45
|
+
data: view.data,
|
|
46
|
+
theme: this.#theme,
|
|
47
|
+
locale: this.#locale,
|
|
48
|
+
access: view.access === "write" ? "write" : "read",
|
|
49
|
+
unicode: this.#unicode,
|
|
50
|
+
dispatch: (action, payload) => this.#surface.dispatch(action, payload),
|
|
51
|
+
view: { values: this.#surface.viewValues },
|
|
52
|
+
setView: (path, value) => this.#surface.setView(path, value),
|
|
53
|
+
serverErrors: this.#surface.serverErrors,
|
|
54
|
+
// The live read (fix wave C3): memoized block components
|
|
55
|
+
// re-resolve the confirmed data + rev off the state, never
|
|
56
|
+
// the construction-time snapshot.
|
|
57
|
+
ctxRefresh: () => {
|
|
58
|
+
const current = this.#surface.view;
|
|
59
|
+
return { data: current.data, rev: current.rev };
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
return this.#ctx;
|
|
63
|
+
}
|
|
64
|
+
const ctx = this.#ctx;
|
|
65
|
+
ctx.data = view.data;
|
|
66
|
+
ctx.access = view.access === "write" ? "write" : "read";
|
|
67
|
+
ctx.serverErrors = this.#surface.serverErrors;
|
|
68
|
+
return ctx;
|
|
69
|
+
}
|
|
70
|
+
render(width) {
|
|
71
|
+
const view = this.#surface.view;
|
|
72
|
+
if (view.status === "loading") {
|
|
73
|
+
return [truncate(this.#theme.dim(SURFACE_LOADING), width)];
|
|
74
|
+
}
|
|
75
|
+
if (view.status === "notFound") {
|
|
76
|
+
return [truncate(this.#theme.dim(SURFACE_NOT_FOUND), width)];
|
|
77
|
+
}
|
|
78
|
+
if (view.status === "error") {
|
|
79
|
+
return [truncate(this.#theme.fg("bad")(SURFACE_ERROR), width)];
|
|
80
|
+
}
|
|
81
|
+
if (view.tree === undefined)
|
|
82
|
+
return [];
|
|
83
|
+
return this.#readyComponent(view.tree).render(width);
|
|
84
|
+
}
|
|
85
|
+
/** Build the ready-state root component on demand (a key can arrive
|
|
86
|
+
* before the first render — the interaction prompter's answer keys
|
|
87
|
+
* must not wait for a paint). */
|
|
88
|
+
#readyComponent(tree) {
|
|
89
|
+
if (this.#component === undefined) {
|
|
90
|
+
this.#component = renderBlock(tree.root, this.#context(), this.#options);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// The component is cached: refresh the shared context so it
|
|
94
|
+
// renders against the CURRENT view (access, data, errors).
|
|
95
|
+
this.#context();
|
|
96
|
+
}
|
|
97
|
+
return this.#component;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Focus the field with this name in the rendered form (the settings
|
|
101
|
+
* view's search-resolve seam, U4 Task 4): forces the component to
|
|
102
|
+
* build (the render gate) and forwards to the form's focus-by-name.
|
|
103
|
+
* Returns false when the surface is not a form or has no such field.
|
|
104
|
+
*/
|
|
105
|
+
focusField(name) {
|
|
106
|
+
const view = this.#surface.view;
|
|
107
|
+
if (view.status !== "ready" || view.tree === undefined)
|
|
108
|
+
return false;
|
|
109
|
+
const focusable = this.#readyComponent(view.tree);
|
|
110
|
+
return focusable.focusName?.(name) === true;
|
|
111
|
+
}
|
|
112
|
+
handleInput(data) {
|
|
113
|
+
const view = this.#surface.view;
|
|
114
|
+
if (view.status === "error") {
|
|
115
|
+
if (data === "r")
|
|
116
|
+
void this.#surface.retry();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (view.status !== "ready" || view.tree === undefined)
|
|
120
|
+
return;
|
|
121
|
+
// Build on demand: a key can arrive before the first paint (the
|
|
122
|
+
// interaction prompter's answer keys route here directly).
|
|
123
|
+
this.#readyComponent(view.tree).handleInput?.(data);
|
|
124
|
+
}
|
|
125
|
+
invalidate() {
|
|
126
|
+
this.#component?.invalidate();
|
|
127
|
+
}
|
|
128
|
+
/** The wrapped component (test introspection AND the interaction
|
|
129
|
+
* prompter's pre-paint key routing: forms expose state). */
|
|
130
|
+
get inner() {
|
|
131
|
+
const view = this.#surface.view;
|
|
132
|
+
if (view.status !== "ready" || view.tree === undefined) {
|
|
133
|
+
return this.#component;
|
|
134
|
+
}
|
|
135
|
+
return this.#readyComponent(view.tree);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SurfaceState (UI-blocks U2 Task 4, spec §6.3): the confirmed-data plus
|
|
3
|
+
* optimistic-overlay model every TUI surface renders through — the TUI
|
|
4
|
+
* port of U1's `useSurface.ts`, with EXACTLY the same overlay rules:
|
|
5
|
+
*
|
|
6
|
+
* - **Confirmed data** changes ONLY from server data or ops (load,
|
|
7
|
+
* fetch, accepted actions carrying ops).
|
|
8
|
+
* - **The overlay** is a list of pending moves `{actionId, payload,
|
|
9
|
+
* target, untilRev?}`, keyed by `actionId`; the rendered `view` is
|
|
10
|
+
* confirmed data with the overlay applied on top.
|
|
11
|
+
* - `{ok: true, rev, ops}` → apply the ops to confirmed, drop the entry.
|
|
12
|
+
* - `{ok: true, rev}` (no ops) → keep the entry with `untilRev = rev`
|
|
13
|
+
* until a fetch at ≥ that rev arrives, then drop it — the item stays
|
|
14
|
+
* where the user dropped it with no snap-back flicker.
|
|
15
|
+
* - `{ok: false}` or the 10 s timeout → drop the entry and surface a
|
|
16
|
+
* NOTICE. U1 toasts; the TUI's notice is a line the app flashes in
|
|
17
|
+
* the status bar — `SurfaceState` collects `notices` and the host
|
|
18
|
+
* drains them through `consumeNotices` (the status-bar wiring is the
|
|
19
|
+
* app's seam, Task 6's surface host).
|
|
20
|
+
* - A `rev` gap (ops land behind the announced rev) or a `DataOpError`
|
|
21
|
+
* applying ops → a full refetch.
|
|
22
|
+
* - `access: "none"` → the not-found state (§6.5); a `treeRev` change
|
|
23
|
+
* → a full reload.
|
|
24
|
+
*
|
|
25
|
+
* The overlay locates a move in the data by scanning the loaded tree
|
|
26
|
+
* for the collection whose `onMove` handler name matches the action —
|
|
27
|
+
* identical to U1 (a move against an unknown handler still goes to the
|
|
28
|
+
* transport, unoverlaid).
|
|
29
|
+
*/
|
|
30
|
+
import type { Action, JsonObject, JsonValue, SurfaceTransport, Tree } from "@sigrun/shared/blocks";
|
|
31
|
+
/** An action has 10 s to answer before it counts as a reject (§6.3). */
|
|
32
|
+
export declare const ACT_TIMEOUT_MS = 10000;
|
|
33
|
+
/** The notice cap: pending notices drop oldest-first past 50 (a flood
|
|
34
|
+
* of failures must not grow the list without bound; the host drains
|
|
35
|
+
* them through `consumeNotices`). */
|
|
36
|
+
export declare const NOTICE_CAP = 50;
|
|
37
|
+
/** The snapshot a render reads (the brief's `ViewState`). */
|
|
38
|
+
export interface SurfaceView {
|
|
39
|
+
readonly status: "loading" | "ready" | "notFound" | "error";
|
|
40
|
+
readonly tree?: Tree;
|
|
41
|
+
/** Confirmed data with the overlay applied — what blocks render. */
|
|
42
|
+
readonly data: JsonObject;
|
|
43
|
+
readonly rev: number;
|
|
44
|
+
readonly treeRev: number;
|
|
45
|
+
readonly access: "none" | "read" | "write";
|
|
46
|
+
/** Pending notice lines (drain via {@link SurfaceState.consumeNotices}). */
|
|
47
|
+
readonly notices: readonly string[];
|
|
48
|
+
}
|
|
49
|
+
/** The surface state container (class shape — the TUI has no hook layer). */
|
|
50
|
+
export declare class SurfaceState {
|
|
51
|
+
#private;
|
|
52
|
+
constructor(transport: SurfaceTransport);
|
|
53
|
+
/** The current snapshot: confirmed data with the overlay applied. */
|
|
54
|
+
get view(): SurfaceView;
|
|
55
|
+
/** Subscribe to state changes (returns the unsubscribe). */
|
|
56
|
+
onChange(cb: () => void): () => void;
|
|
57
|
+
/** Drain the pending notices (the host flashes them in the status bar). */
|
|
58
|
+
consumeNotices(): readonly string[];
|
|
59
|
+
/** Flip the live surface's access (a mid-edit settle swap renders
|
|
60
|
+
* read-only: the transport's access was captured at load, so the
|
|
61
|
+
* settle path flips the state directly — the view re-renders via
|
|
62
|
+
* the emitted change). Only narrows: once read, never back. */
|
|
63
|
+
setAccess(access: "read" | "write"): void;
|
|
64
|
+
/** The channel-owned `/view/` branch (spec §6.2) — a BlockCtx's
|
|
65
|
+
* `view.values` reads this object. */
|
|
66
|
+
get viewValues(): JsonObject;
|
|
67
|
+
/** The server field errors from the latest rejected action. */
|
|
68
|
+
get serverErrors(): Readonly<Record<string, string>>;
|
|
69
|
+
/** Write a pointer under the `/view/` branch (the form's live
|
|
70
|
+
* `/view/`-bound flush lands here; the write never dispatches). */
|
|
71
|
+
setView(path: string, value: JsonValue): void;
|
|
72
|
+
/** Reload after an error (SurfaceView's `r` key). */
|
|
73
|
+
retry(): Promise<void>;
|
|
74
|
+
/** Load the tree and initial data; subscribe to change events. */
|
|
75
|
+
load(): Promise<void>;
|
|
76
|
+
/** Stop listening and cancel every pending action timer. */
|
|
77
|
+
dispose(): void;
|
|
78
|
+
/** Dispatch an action to the surface; a move joins the overlay first. */
|
|
79
|
+
dispatch(action: Action, payload: JsonValue): void;
|
|
80
|
+
}
|