@north-light/crouter 0.3.235 → 0.3.237
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/dist/api/client.d.ts +2 -1
- package/dist/api/client.js +3 -0
- package/dist/api/dto/broker-ops.d.ts +1 -1
- package/dist/api/dto/common.d.ts +2 -0
- package/dist/api/dto/lifecycle.d.ts +9 -2
- package/dist/api/dto/messages.d.ts +5 -0
- package/dist/api/dto/nodes.d.ts +14 -5
- package/dist/api/dto/worktree.d.ts +10 -0
- package/dist/api/routes.d.ts +1 -0
- package/dist/api/routes.js +1 -0
- package/dist/builtin-memory/00-runtime-base/00-authoring.md +1 -1
- package/dist/builtin-memory/internal/agent-shaping.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/sysprompt-window.ts +1 -1
- package/dist/clients/attach/render/tool-calls.js +18 -3
- package/dist/clients/attach/viewer.js +399 -399
- package/dist/clients/inbox/review/__tests__/editor-roundtrip.test.js +6 -0
- package/dist/clients/inbox/review/roundtrip.js +1 -1
- package/dist/commands/__tests__/node-new.test.js +58 -0
- package/dist/commands/memory/lint.js +22 -13
- package/dist/commands/memory/shared.js +1 -1
- package/dist/commands/node/create.d.ts +1 -1
- package/dist/commands/node/create.js +4 -1
- package/dist/commands/node/lifecycle.js +2 -2
- package/dist/commands/node-worktree.js +39 -8
- package/dist/commands/revive.js +11 -2
- package/dist/commands/sys/context/admin/actions.d.ts +48 -0
- package/dist/commands/sys/context/admin/actions.js +246 -0
- package/dist/commands/sys/context/admin/axes-panel.d.ts +21 -0
- package/dist/commands/sys/context/admin/axes-panel.js +117 -0
- package/dist/commands/sys/context/admin/detail-panel.d.ts +53 -0
- package/dist/commands/sys/context/admin/detail-panel.js +178 -0
- package/dist/commands/sys/context/admin/docs-panel.d.ts +45 -0
- package/dist/commands/sys/context/admin/docs-panel.js +186 -0
- package/dist/commands/sys/context/admin/model.d.ts +75 -0
- package/dist/commands/sys/context/admin/model.js +209 -0
- package/dist/commands/sys/context/admin/shell.d.ts +82 -0
- package/dist/commands/sys/context/admin/shell.js +669 -0
- package/dist/commands/sys/context/admin.d.ts +1 -0
- package/dist/commands/sys/context/admin.js +124 -0
- package/dist/commands/sys/context/doc.d.ts +1 -0
- package/dist/commands/sys/context/doc.js +196 -0
- package/dist/commands/sys/context/prompt-review.d.ts +1 -0
- package/dist/commands/sys/{prompt-review.js → context/prompt-review.js} +10 -10
- package/dist/commands/sys/context/resolve.d.ts +55 -0
- package/dist/commands/sys/context/resolve.js +428 -0
- package/dist/commands/sys/context/sysprompt.d.ts +1 -0
- package/dist/commands/sys/{sysprompt.js → context/sysprompt.js} +11 -11
- package/dist/commands/sys/context.d.ts +2 -0
- package/dist/commands/sys/context.js +20 -0
- package/dist/commands/sys.js +3 -4
- package/dist/core/__tests__/canvas-inbox-watcher-naming.test.d.ts +1 -0
- package/dist/core/__tests__/canvas-inbox-watcher-naming.test.js +258 -0
- package/dist/core/__tests__/child-death-wake.test.js +38 -22
- package/dist/core/__tests__/cron-self-cancel-delivery.test.d.ts +1 -0
- package/dist/core/__tests__/cron-self-cancel-delivery.test.js +72 -0
- package/dist/core/__tests__/daemon-boot.test.js +40 -28
- package/dist/core/__tests__/helpers/harness.js +1 -0
- package/dist/core/__tests__/human-deliver.test.js +1 -1
- package/dist/core/__tests__/human-node-not-supervised.test.js +10 -15
- package/dist/core/__tests__/integration/revive.test.js +12 -12
- package/dist/core/__tests__/integration/spawn-root.test.js +6 -0
- package/dist/core/__tests__/integration/worktree-reap.test.js +32 -0
- package/dist/core/__tests__/lifecycle-terminal-reasons.test.d.ts +1 -0
- package/dist/core/__tests__/lifecycle-terminal-reasons.test.js +61 -0
- package/dist/core/__tests__/migration.test.js +38 -1
- package/dist/core/__tests__/push-final-guard.test.js +1 -1
- package/dist/core/__tests__/revive-capacity.test.d.ts +1 -0
- package/dist/core/__tests__/revive-capacity.test.js +127 -0
- package/dist/core/__tests__/revive-parked-fresh.test.js +7 -7
- package/dist/core/__tests__/seam/broker-cap-freeze.test.d.ts +1 -0
- package/dist/core/__tests__/seam/broker-cap-freeze.test.js +81 -0
- package/dist/core/__tests__/seam/dormancy-release.test.js +76 -7
- package/dist/core/__tests__/seam/yield-refresh-transaction.test.js +1 -1
- package/dist/core/canvas/browse/render.js +11 -3
- package/dist/core/canvas/canvas.d.ts +15 -3
- package/dist/core/canvas/canvas.js +52 -16
- package/dist/core/canvas/migrations.js +21 -0
- package/dist/core/canvas/nav-render.js +3 -1
- package/dist/core/canvas/remote-canvas-source.js +2 -0
- package/dist/core/canvas/render-source.d.ts +3 -1
- package/dist/core/canvas/render-source.js +7 -3
- package/dist/core/canvas/status-glyph.d.ts +10 -1
- package/dist/core/canvas/status-glyph.js +19 -3
- package/dist/core/canvas/types.d.ts +22 -3
- package/dist/core/feed/feed.js +1 -1
- package/dist/core/human/feedback-companion.js +5 -2
- package/dist/core/memory/lint.d.ts +19 -0
- package/dist/core/memory/lint.js +97 -0
- package/dist/core/memory-resolver.d.ts +4 -0
- package/dist/core/memory-resolver.js +7 -7
- package/dist/core/preview-registry.js +4 -4
- package/dist/core/review/realize.js +4 -2
- package/dist/core/runtime/close.js +1 -1
- package/dist/core/runtime/fleet.d.ts +29 -1
- package/dist/core/runtime/fleet.js +23 -0
- package/dist/core/runtime/host.js +32 -11
- package/dist/core/runtime/launch-prompt.d.ts +1 -1
- package/dist/core/runtime/launch-prompt.js +2 -2
- package/dist/core/runtime/launch-target.js +1 -1
- package/dist/core/runtime/lifecycle.d.ts +4 -2
- package/dist/core/runtime/lifecycle.js +12 -1
- package/dist/core/runtime/memory.js +1 -1
- package/dist/core/runtime/reopen.js +10 -1
- package/dist/core/runtime/reset.js +1 -1
- package/dist/core/runtime/revive-all.d.ts +3 -0
- package/dist/core/runtime/revive-all.js +10 -4
- package/dist/core/runtime/revive.d.ts +12 -0
- package/dist/core/runtime/revive.js +62 -3
- package/dist/core/runtime/spawn.d.ts +9 -13
- package/dist/core/runtime/spawn.js +40 -17
- package/dist/core/substrate/frontmatter-validation.js +1 -1
- package/dist/core/substrate/index.d.ts +6 -3
- package/dist/core/substrate/index.js +4 -3
- package/dist/core/substrate/on-read.d.ts +0 -8
- package/dist/core/substrate/on-read.js +45 -124
- package/dist/core/substrate/plan.d.ts +94 -0
- package/dist/core/substrate/plan.js +266 -0
- package/dist/core/substrate/render.d.ts +5 -6
- package/dist/core/substrate/render.js +76 -129
- package/dist/core/substrate/schema.d.ts +1 -6
- package/dist/core/substrate/schema.js +1 -15
- package/dist/core/substrate/session-cache.d.ts +2 -10
- package/dist/core/substrate/session-cache.js +9 -47
- package/dist/core/substrate/subject-fields.d.ts +1 -4
- package/dist/core/substrate/subject-fields.js +2 -5
- package/dist/core/substrate/surface-match.d.ts +37 -17
- package/dist/core/substrate/surface-match.js +44 -53
- package/dist/core/tui/page-host.d.ts +6 -0
- package/dist/core/tui/page-host.js +12 -3
- package/dist/core/worktree.d.ts +8 -0
- package/dist/core/worktree.js +65 -0
- package/dist/daemon/__tests__/reconciler-signature.test.js +3 -2
- package/dist/daemon/__tests__/thaw-order.test.d.ts +1 -0
- package/dist/daemon/__tests__/thaw-order.test.js +55 -0
- package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.js +6 -0
- package/dist/daemon/api/__tests__/node-create-description.test.js +6 -0
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +1 -1
- package/dist/daemon/api/bridge.js +5 -1
- package/dist/daemon/api/handlers/attach.js +5 -1
- package/dist/daemon/api/handlers/bash-jobs.js +1 -1
- package/dist/daemon/api/handlers/messages.js +10 -2
- package/dist/daemon/api/handlers/nodes.js +8 -4
- package/dist/daemon/api/handlers/worktree.js +10 -1
- package/dist/daemon/api/map.js +7 -0
- package/dist/daemon/companion-retire.js +1 -1
- package/dist/daemon/cron/passes.d.ts +0 -5
- package/dist/daemon/cron/passes.js +0 -20
- package/dist/daemon/cron/sinks.js +1 -1
- package/dist/daemon/cron-run.d.ts +5 -6
- package/dist/daemon/cron-run.js +7 -16
- package/dist/daemon/crtrd.d.ts +0 -10
- package/dist/daemon/crtrd.js +23 -61
- package/dist/daemon/fleet.d.ts +10 -7
- package/dist/daemon/fleet.js +71 -12
- package/dist/daemon/messaging/node-message.js +3 -1
- package/dist/daemon/profile-delete.js +1 -1
- package/dist/daemon/reconcilers/broker-supervision.d.ts +26 -9
- package/dist/daemon/reconcilers/broker-supervision.js +39 -52
- package/dist/daemon/reconcilers/controller-death.d.ts +7 -0
- package/dist/daemon/reconcilers/controller-death.js +26 -0
- package/dist/daemon/reconcilers/cron-lane.d.ts +0 -3
- package/dist/daemon/reconcilers/cron-lane.js +0 -3
- package/dist/daemon/reconcilers/live-obligation.js +3 -1
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +53 -0
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +170 -0
- package/dist/daemon/reconcilers/node-lifecycle/respawn-policy.d.ts +5 -0
- package/dist/daemon/reconcilers/node-lifecycle/respawn-policy.js +16 -0
- package/dist/daemon/reconcilers/node-lifecycle/terminating.d.ts +4 -0
- package/dist/daemon/reconcilers/node-lifecycle/terminating.js +13 -0
- package/dist/daemon/reconcilers/node-lifecycle/tick.d.ts +26 -0
- package/dist/daemon/reconcilers/node-lifecycle/tick.js +140 -0
- package/dist/pi-extensions/canvas-inbox-watcher.js +12 -0
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/commands/sys/prompt-review.d.ts +0 -1
- package/dist/commands/sys/sysprompt.d.ts +0 -1
- package/dist/core/__tests__/cron-broker-capacity.test.js +0 -119
- package/dist/daemon/reconcilers/dormant-inbox.d.ts +0 -18
- package/dist/daemon/reconcilers/dormant-inbox.js +0 -157
- /package/dist/{core/__tests__/cron-broker-capacity.test.d.ts → commands/__tests__/node-new.test.d.ts} +0 -0
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
// The `crtr sys context admin` page: a snapshot rail, the document matrix, and
|
|
2
|
+
// the dossier that edits one document's routing. The rail decides WHAT is being
|
|
3
|
+
// planned, the matrix shows WHERE every document lands, and the dossier is the
|
|
4
|
+
// only place a write starts.
|
|
5
|
+
import { Input, matchesKey, truncateToWidth, visibleWidth, } from '@earendil-works/pi-tui';
|
|
6
|
+
import { formatGesture, matchesPiTuiInput, } from '../../../../core/keybindings/index.js';
|
|
7
|
+
import { runTuiPage } from '../../../../core/tui/page-host.js';
|
|
8
|
+
import { PAGE_CHROME_ROWS, columns, framed, pageFrame, theme, } from '../../../../core/tui/panel.js';
|
|
9
|
+
import { RUNGS, SURFACE_EVENTS, SURFACE_RUNGS, } from '../../../../core/substrate/schema.js';
|
|
10
|
+
import { profileNameFor } from '../../../../core/substrate/subject-fields.js';
|
|
11
|
+
import { editBufferInEditor } from '../../../../clients/inbox/review/roundtrip.js';
|
|
12
|
+
import { capOwnerFor, moveDoc, readBody, readOnlyReason, toggleFlag, writeBody, writeCap, writeGate, writeRoutingLine, writeShortForm, writeSurfaces, } from './actions.js';
|
|
13
|
+
import { axesOf, cycleAxis, setTypedAxis, renderAxes, typedAxisValue } from './axes-panel.js';
|
|
14
|
+
import { ContextDetailPanel } from './detail-panel.js';
|
|
15
|
+
import { ContextDocsPanel, buildRows } from './docs-panel.js';
|
|
16
|
+
import { PlanSet, plural } from './model.js';
|
|
17
|
+
const RAIL_WIDTH = 26;
|
|
18
|
+
const DOCS_WIDTH = 38;
|
|
19
|
+
const RAIL_MIN_TOTAL_WIDTH = 86;
|
|
20
|
+
const THREE_COLUMN_MIN_WIDTH = 112;
|
|
21
|
+
export class ContextAdminShell {
|
|
22
|
+
tui;
|
|
23
|
+
terminalRows;
|
|
24
|
+
bindings;
|
|
25
|
+
suspendForChild;
|
|
26
|
+
snapshot;
|
|
27
|
+
plans;
|
|
28
|
+
zone = 'axes';
|
|
29
|
+
axisIndex = 0;
|
|
30
|
+
docs;
|
|
31
|
+
detail = new ContextDetailPanel();
|
|
32
|
+
modal;
|
|
33
|
+
notice;
|
|
34
|
+
busy;
|
|
35
|
+
edited = new Set();
|
|
36
|
+
capChanges = [];
|
|
37
|
+
_focused = false;
|
|
38
|
+
onCancel;
|
|
39
|
+
onSubmit;
|
|
40
|
+
constructor(seed, tui, terminalRows, bindings, suspendForChild) {
|
|
41
|
+
this.tui = tui;
|
|
42
|
+
this.terminalRows = terminalRows;
|
|
43
|
+
this.bindings = bindings;
|
|
44
|
+
this.suspendForChild = suspendForChild;
|
|
45
|
+
this.snapshot = seed;
|
|
46
|
+
this.plans = new PlanSet(seed);
|
|
47
|
+
this.docs = new ContextDocsPanel(() => this.tui.requestRender());
|
|
48
|
+
this.rebuild();
|
|
49
|
+
}
|
|
50
|
+
set focused(value) {
|
|
51
|
+
this._focused = value;
|
|
52
|
+
}
|
|
53
|
+
get focused() {
|
|
54
|
+
return this._focused;
|
|
55
|
+
}
|
|
56
|
+
/** Required by `Component`. Nothing is cached between frames: a plan can land
|
|
57
|
+
* from a warm tick and an edit can land from an await, both between inputs. */
|
|
58
|
+
invalidate() { }
|
|
59
|
+
dispose() {
|
|
60
|
+
this.plans.dispose();
|
|
61
|
+
}
|
|
62
|
+
// planning
|
|
63
|
+
/** Recompute the focused event's plan, refresh the panels from it, and let
|
|
64
|
+
* the remaining events fill in behind it. */
|
|
65
|
+
rebuild(selectedKey) {
|
|
66
|
+
this.plans.plan(this.snapshot.event);
|
|
67
|
+
this.docs.setRows(buildRows(this.plans, this.snapshot.event), selectedKey ?? this.docs.selected?.key);
|
|
68
|
+
this.syncDetail();
|
|
69
|
+
this.plans.warm(() => {
|
|
70
|
+
this.docs.setRows(buildRows(this.plans, this.snapshot.event), this.docs.selected?.key);
|
|
71
|
+
this.syncDetail();
|
|
72
|
+
this.tui.requestRender();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
syncDetail() {
|
|
76
|
+
const row = this.docs.selected;
|
|
77
|
+
const capOwner = row === undefined ? null : capOwnerFor(row.anchor, this.snapshot.profileId);
|
|
78
|
+
const readOnly = row === undefined ? null : readOnlyReason(row.anchor, this.snapshot.profileId);
|
|
79
|
+
this.detail.setContext(row, this.snapshot.event, capOwner, readOnly);
|
|
80
|
+
}
|
|
81
|
+
setSnapshot(next) {
|
|
82
|
+
this.snapshot = next;
|
|
83
|
+
this.plans.setSnapshot(next);
|
|
84
|
+
this.notice = undefined;
|
|
85
|
+
this.rebuild();
|
|
86
|
+
}
|
|
87
|
+
// input
|
|
88
|
+
matches(id, data) {
|
|
89
|
+
return matchesPiTuiInput(this.bindings, id, data, (input, gesture) => matchesKey(input, gesture));
|
|
90
|
+
}
|
|
91
|
+
label(id, fallback) {
|
|
92
|
+
const gestures = this.bindings.gestures(id);
|
|
93
|
+
return gestures.length === 0 ? fallback : formatGesture(gestures[0].split(' > ').pop());
|
|
94
|
+
}
|
|
95
|
+
isDown(data) {
|
|
96
|
+
return matchesKey(data, 'j') || matchesKey(data, 'down') || this.matches('crtr.setup.list-down', data);
|
|
97
|
+
}
|
|
98
|
+
isUp(data) {
|
|
99
|
+
return matchesKey(data, 'k') || matchesKey(data, 'up') || this.matches('crtr.setup.list-up', data);
|
|
100
|
+
}
|
|
101
|
+
isDescend(data) {
|
|
102
|
+
return (matchesKey(data, 'l') ||
|
|
103
|
+
matchesKey(data, 'right') ||
|
|
104
|
+
matchesKey(data, 'enter') ||
|
|
105
|
+
this.matches('crtr.setup.advance', data));
|
|
106
|
+
}
|
|
107
|
+
isAscend(data) {
|
|
108
|
+
return matchesKey(data, 'h') || matchesKey(data, 'left');
|
|
109
|
+
}
|
|
110
|
+
/** Tab steps the focused event from any zone — the one axis every panel is
|
|
111
|
+
* drawn against, so it earns the page-level gesture. */
|
|
112
|
+
cyclesEvent(data) {
|
|
113
|
+
const horizontal = matchesKey(data, 'right') || matchesKey(data, 'left');
|
|
114
|
+
if (matchesKey(data, 'tab') || (!horizontal && this.matches('crtr.setup.tab-next', data)))
|
|
115
|
+
return 1;
|
|
116
|
+
if (matchesKey(data, 'shift+tab') || (!horizontal && this.matches('crtr.setup.tab-previous', data)))
|
|
117
|
+
return -1;
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
handleInput(data) {
|
|
121
|
+
if (this.busy !== undefined)
|
|
122
|
+
return;
|
|
123
|
+
if (this.modal !== undefined) {
|
|
124
|
+
this.handleModal(data);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (this.docs.ownsKeyboard && this.docs.handleInput(data)) {
|
|
128
|
+
this.syncDetail();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const step = this.cyclesEvent(data);
|
|
132
|
+
if (step !== 0) {
|
|
133
|
+
this.setSnapshot(cycleAxis(this.snapshot, 'event', step));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (matchesKey(data, 'q') || this.matches('crtr.setup.cancel', data)) {
|
|
137
|
+
this.close();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (this.zone === 'axes')
|
|
141
|
+
this.handleAxes(data);
|
|
142
|
+
else if (this.zone === 'docs')
|
|
143
|
+
this.handleDocs(data);
|
|
144
|
+
else
|
|
145
|
+
this.handleDetail(data);
|
|
146
|
+
}
|
|
147
|
+
handleAxes(data) {
|
|
148
|
+
const axes = axesOf(this.snapshot, profileNameFor(this.snapshot.profileId) ?? 'none');
|
|
149
|
+
this.axisIndex = Math.max(0, Math.min(this.axisIndex, axes.length - 1));
|
|
150
|
+
const axis = axes[this.axisIndex];
|
|
151
|
+
if (this.isDown(data)) {
|
|
152
|
+
this.axisIndex = Math.min(axes.length - 1, this.axisIndex + 1);
|
|
153
|
+
}
|
|
154
|
+
else if (this.isUp(data)) {
|
|
155
|
+
this.axisIndex = Math.max(0, this.axisIndex - 1);
|
|
156
|
+
}
|
|
157
|
+
else if (matchesKey(data, 'space') || this.matches('crtr.setup.toggle', data)) {
|
|
158
|
+
if (axis.typed)
|
|
159
|
+
this.openAxisField(axis.id, axis.label);
|
|
160
|
+
else
|
|
161
|
+
this.setSnapshot(cycleAxis(this.snapshot, axis.id, 1));
|
|
162
|
+
}
|
|
163
|
+
else if (this.isDescend(data)) {
|
|
164
|
+
if (matchesKey(data, 'enter') && axis.typed)
|
|
165
|
+
this.openAxisField(axis.id, axis.label);
|
|
166
|
+
else
|
|
167
|
+
this.zone = 'docs';
|
|
168
|
+
}
|
|
169
|
+
else if (matchesKey(data, 'escape')) {
|
|
170
|
+
this.close();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
handleDocs(data) {
|
|
174
|
+
if (this.docs.handleInput(data)) {
|
|
175
|
+
this.syncDetail();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (this.isDown(data)) {
|
|
179
|
+
this.docs.move(1);
|
|
180
|
+
this.syncDetail();
|
|
181
|
+
}
|
|
182
|
+
else if (this.isUp(data)) {
|
|
183
|
+
this.docs.move(-1);
|
|
184
|
+
this.syncDetail();
|
|
185
|
+
}
|
|
186
|
+
else if (matchesKey(data, 'd')) {
|
|
187
|
+
this.docs.toggleDeliveringOnly();
|
|
188
|
+
this.syncDetail();
|
|
189
|
+
}
|
|
190
|
+
else if (this.isDescend(data)) {
|
|
191
|
+
if (this.docs.selected !== undefined)
|
|
192
|
+
this.zone = 'detail';
|
|
193
|
+
}
|
|
194
|
+
else if (this.isAscend(data) || matchesKey(data, 'escape')) {
|
|
195
|
+
this.zone = 'axes';
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
handleDetail(data) {
|
|
199
|
+
if (this.isDown(data))
|
|
200
|
+
this.detail.move(1);
|
|
201
|
+
else if (this.isUp(data))
|
|
202
|
+
this.detail.move(-1);
|
|
203
|
+
else if (this.isAscend(data) || matchesKey(data, 'escape'))
|
|
204
|
+
this.zone = 'docs';
|
|
205
|
+
else if (matchesKey(data, 'enter') || matchesKey(data, 'space') || this.matches('crtr.setup.toggle', data)) {
|
|
206
|
+
this.activate();
|
|
207
|
+
}
|
|
208
|
+
else if (matchesKey(data, 'b'))
|
|
209
|
+
this.editBody();
|
|
210
|
+
else if (matchesKey(data, 'x'))
|
|
211
|
+
this.removeEntry();
|
|
212
|
+
else if (matchesKey(data, 'e'))
|
|
213
|
+
this.composeEntry();
|
|
214
|
+
else if (matchesKey(data, 'g'))
|
|
215
|
+
this.editGate();
|
|
216
|
+
else if (matchesKey(data, 'u'))
|
|
217
|
+
this.flip('unlisted');
|
|
218
|
+
else if (matchesKey(data, 's'))
|
|
219
|
+
this.flip('slash');
|
|
220
|
+
else if (matchesKey(data, 'm'))
|
|
221
|
+
this.editCap();
|
|
222
|
+
else if (matchesKey(data, 'r'))
|
|
223
|
+
this.editMove();
|
|
224
|
+
else if (matchesKey(data, 'w'))
|
|
225
|
+
this.editRouting();
|
|
226
|
+
else if (matchesKey(data, 'f'))
|
|
227
|
+
this.editShortForm();
|
|
228
|
+
}
|
|
229
|
+
/** What the focused dossier row does when opened — the same write its letter
|
|
230
|
+
* key starts, so a user who never learns the letters can still edit. */
|
|
231
|
+
activate() {
|
|
232
|
+
const row = this.detail.selected;
|
|
233
|
+
if (row === undefined)
|
|
234
|
+
return;
|
|
235
|
+
switch (row.kind) {
|
|
236
|
+
case 'entry': return this.cycleEntryRung(row.at);
|
|
237
|
+
case 'add-entry': return this.composeEntry();
|
|
238
|
+
case 'gate': return this.editGate();
|
|
239
|
+
case 'unlisted': return this.flip('unlisted');
|
|
240
|
+
case 'slash': return this.flip('slash');
|
|
241
|
+
case 'routing': return this.editRouting();
|
|
242
|
+
case 'short-form': return this.editShortForm();
|
|
243
|
+
case 'body': return this.editBody();
|
|
244
|
+
case 'cap': return this.editCap();
|
|
245
|
+
case 'move': return this.editMove();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// writes
|
|
249
|
+
get record() {
|
|
250
|
+
return this.docs.selected?.anchor;
|
|
251
|
+
}
|
|
252
|
+
apply(work) {
|
|
253
|
+
this.busy = 'writing…';
|
|
254
|
+
this.tui.requestRender();
|
|
255
|
+
void work.then((outcome) => {
|
|
256
|
+
this.busy = undefined;
|
|
257
|
+
this.notice = { text: outcome.message, ok: outcome.ok };
|
|
258
|
+
if (outcome.ok) {
|
|
259
|
+
if (outcome.name !== undefined)
|
|
260
|
+
this.edited.add(outcome.name);
|
|
261
|
+
this.plans.invalidate();
|
|
262
|
+
this.rebuild();
|
|
263
|
+
}
|
|
264
|
+
this.tui.requestRender();
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
cycleEntryRung(at) {
|
|
268
|
+
const record = this.record;
|
|
269
|
+
const entry = record?.doc.surfaces[at];
|
|
270
|
+
if (record === undefined || entry === undefined)
|
|
271
|
+
return;
|
|
272
|
+
const next = SURFACE_RUNGS[(SURFACE_RUNGS.indexOf(entry.at) + 1) % SURFACE_RUNGS.length];
|
|
273
|
+
const surfaces = record.doc.surfaces.map((held, index) => (index === at ? { ...held, at: next } : held));
|
|
274
|
+
this.apply(writeSurfaces(record, this.snapshot.profileId, surfaces, `${record.name} now delivers at "${next}" on ${entry.on}.`));
|
|
275
|
+
}
|
|
276
|
+
removeEntry() {
|
|
277
|
+
const record = this.record;
|
|
278
|
+
const at = this.detail.selectedEntry;
|
|
279
|
+
if (record === undefined || at === undefined) {
|
|
280
|
+
this.notice = { text: 'Select a surface entry to remove.', ok: false };
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const entry = record.doc.surfaces[at];
|
|
284
|
+
const surfaces = record.doc.surfaces.filter((_held, index) => index !== at);
|
|
285
|
+
this.modal = {
|
|
286
|
+
kind: 'confirm',
|
|
287
|
+
title: 'Remove surface entry',
|
|
288
|
+
message: `${record.name} will stop delivering on ${entry.on} at "${entry.at}".`,
|
|
289
|
+
submit: () => this.apply(writeSurfaces(record, this.snapshot.profileId, surfaces, `Removed the ${entry.on} entry from ${record.name}.`)),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/** Event, then match, then rung — the three fields a valid entry needs, asked
|
|
293
|
+
* in the order the schema reads them. */
|
|
294
|
+
composeEntry() {
|
|
295
|
+
const record = this.record;
|
|
296
|
+
if (record === undefined)
|
|
297
|
+
return;
|
|
298
|
+
this.modal = {
|
|
299
|
+
kind: 'choice',
|
|
300
|
+
title: 'New entry — event',
|
|
301
|
+
options: [...SURFACE_EVENTS],
|
|
302
|
+
index: SURFACE_EVENTS.indexOf(this.snapshot.event),
|
|
303
|
+
submit: (event) => {
|
|
304
|
+
const on = event;
|
|
305
|
+
const needsMatch = on !== 'boot' && on !== 'workspace-open';
|
|
306
|
+
const chooseRung = (match) => {
|
|
307
|
+
this.modal = {
|
|
308
|
+
kind: 'choice',
|
|
309
|
+
title: 'New entry — rung',
|
|
310
|
+
options: [...SURFACE_RUNGS],
|
|
311
|
+
index: 0,
|
|
312
|
+
submit: (rung) => {
|
|
313
|
+
this.modal = undefined;
|
|
314
|
+
const entry = {
|
|
315
|
+
on,
|
|
316
|
+
...(match === '' ? {} : { match: [match] }),
|
|
317
|
+
at: rung,
|
|
318
|
+
};
|
|
319
|
+
this.apply(writeSurfaces(record, this.snapshot.profileId, [...record.doc.surfaces, entry], `${record.name} now delivers on ${on} at "${rung}".`));
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
if (!needsMatch) {
|
|
324
|
+
chooseRung('');
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
this.modal = {
|
|
328
|
+
kind: 'field',
|
|
329
|
+
title: `New entry — ${on} match`,
|
|
330
|
+
hint: on === 'read' ? 'absolute path glob, or ./ for this document\u2019s own directory' : 'glob over the event\u2019s namespace',
|
|
331
|
+
input: seeded(''),
|
|
332
|
+
submit: (value) => {
|
|
333
|
+
const match = value.trim();
|
|
334
|
+
if (match === '') {
|
|
335
|
+
this.modal = undefined;
|
|
336
|
+
this.notice = { text: `A ${on} entry needs a match glob.`, ok: false };
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
chooseRung(match);
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
editGate() {
|
|
346
|
+
const record = this.record;
|
|
347
|
+
if (record === undefined)
|
|
348
|
+
return;
|
|
349
|
+
this.modal = {
|
|
350
|
+
kind: 'field',
|
|
351
|
+
title: 'Document gate',
|
|
352
|
+
hint: 'YAML or JSON predicate over the node config — empty clears it',
|
|
353
|
+
input: seeded(record.doc.gate === undefined ? '' : JSON.stringify(record.doc.gate)),
|
|
354
|
+
submit: (value) => {
|
|
355
|
+
this.modal = undefined;
|
|
356
|
+
this.apply(writeGate(record, this.snapshot.profileId, value));
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
flip(field) {
|
|
361
|
+
const record = this.record;
|
|
362
|
+
if (record === undefined)
|
|
363
|
+
return;
|
|
364
|
+
this.apply(toggleFlag(record, this.snapshot.profileId, field));
|
|
365
|
+
}
|
|
366
|
+
editRouting() {
|
|
367
|
+
const record = this.record;
|
|
368
|
+
if (record === undefined)
|
|
369
|
+
return;
|
|
370
|
+
this.modal = {
|
|
371
|
+
kind: 'field',
|
|
372
|
+
title: 'Routing line',
|
|
373
|
+
hint: 'When <circumstance>, this <kind> should be read because <payoff>',
|
|
374
|
+
input: seeded(record.doc.whenAndWhyToRead),
|
|
375
|
+
submit: (value) => {
|
|
376
|
+
this.modal = undefined;
|
|
377
|
+
this.apply(writeRoutingLine(record, this.snapshot.profileId, value));
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
editShortForm() {
|
|
382
|
+
const record = this.record;
|
|
383
|
+
if (record === undefined)
|
|
384
|
+
return;
|
|
385
|
+
this.modal = {
|
|
386
|
+
kind: 'field',
|
|
387
|
+
title: 'Short form',
|
|
388
|
+
hint: 'human-facing abbreviation for listings — empty clears it',
|
|
389
|
+
input: seeded(record.doc.shortForm),
|
|
390
|
+
submit: (value) => {
|
|
391
|
+
this.modal = undefined;
|
|
392
|
+
this.apply(writeShortForm(record, this.snapshot.profileId, value));
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
/** The one write that leaves the page: the terminal goes to $EDITOR and comes
|
|
397
|
+
* back repainted, and the buffer that returns is saved through `memory edit`
|
|
398
|
+
* like every other field here. */
|
|
399
|
+
editBody() {
|
|
400
|
+
const record = this.record;
|
|
401
|
+
if (record === undefined)
|
|
402
|
+
return;
|
|
403
|
+
// Checked before the terminal leaves for $EDITOR, so a read-only record
|
|
404
|
+
// refuses in place rather than after a whole editing session.
|
|
405
|
+
const refusal = readOnlyReason(record, this.snapshot.profileId);
|
|
406
|
+
if (refusal !== null) {
|
|
407
|
+
this.notice = { text: refusal, ok: false };
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
let before;
|
|
411
|
+
try {
|
|
412
|
+
before = readBody(record);
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
this.notice = { text: error instanceof Error ? error.message : String(error), ok: false };
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const resume = this.suspendForChild();
|
|
419
|
+
let result;
|
|
420
|
+
try {
|
|
421
|
+
result = editBufferInEditor(before);
|
|
422
|
+
}
|
|
423
|
+
finally {
|
|
424
|
+
resume();
|
|
425
|
+
}
|
|
426
|
+
if (result.error !== undefined) {
|
|
427
|
+
this.notice = { text: result.error, ok: false };
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (result.text === before) {
|
|
431
|
+
this.notice = { text: `${record.name} came back unchanged \u2014 nothing written.`, ok: false };
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
this.apply(writeBody(record, this.snapshot.profileId, result.text));
|
|
435
|
+
}
|
|
436
|
+
editMove() {
|
|
437
|
+
const record = this.record;
|
|
438
|
+
if (record === undefined)
|
|
439
|
+
return;
|
|
440
|
+
this.modal = {
|
|
441
|
+
kind: 'field',
|
|
442
|
+
title: 'Move or rename',
|
|
443
|
+
hint: 'the new canonical name inside this document\u2019s own store',
|
|
444
|
+
input: seeded(record.name),
|
|
445
|
+
submit: (value) => {
|
|
446
|
+
this.modal = undefined;
|
|
447
|
+
this.apply(moveDoc(record, this.snapshot.profileId, value));
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
/** The ceiling belongs to the profile project entry that governs the store,
|
|
452
|
+
* not to the document, so this write is offered only when one exists. */
|
|
453
|
+
editCap() {
|
|
454
|
+
const record = this.record;
|
|
455
|
+
const profileId = this.snapshot.profileId;
|
|
456
|
+
if (record === undefined)
|
|
457
|
+
return;
|
|
458
|
+
const owner = profileId === null ? null : capOwnerFor(record, profileId);
|
|
459
|
+
if (owner === null || profileId === null) {
|
|
460
|
+
this.notice = { text: 'No profile project governs this store — its ceiling is neutral and not editable here.', ok: false };
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
this.modal = {
|
|
464
|
+
kind: 'choice',
|
|
465
|
+
title: `Ceiling for ${owner.path}`,
|
|
466
|
+
options: [...RUNGS],
|
|
467
|
+
index: Math.max(0, RUNGS.indexOf(owner.memory)),
|
|
468
|
+
submit: (rung) => {
|
|
469
|
+
this.modal = undefined;
|
|
470
|
+
const outcome = writeCap(profileId, owner.path, rung);
|
|
471
|
+
this.notice = { text: outcome.message, ok: outcome.ok };
|
|
472
|
+
if (outcome.change !== undefined) {
|
|
473
|
+
const held = this.capChanges.findIndex((change) => change.path === outcome.change.path);
|
|
474
|
+
if (held >= 0)
|
|
475
|
+
this.capChanges[held] = outcome.change;
|
|
476
|
+
else
|
|
477
|
+
this.capChanges.push(outcome.change);
|
|
478
|
+
this.plans.invalidate();
|
|
479
|
+
this.rebuild();
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
openAxisField(id, label) {
|
|
485
|
+
this.modal = {
|
|
486
|
+
kind: 'field',
|
|
487
|
+
title: label,
|
|
488
|
+
hint: id === 'cwd' ? 'absolute directory the project stores resolve from' : 'value the planner should match against',
|
|
489
|
+
input: seeded(typedAxisValue(this.snapshot, id)),
|
|
490
|
+
submit: (value) => {
|
|
491
|
+
this.modal = undefined;
|
|
492
|
+
this.setSnapshot(setTypedAxis(this.snapshot, id, value));
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
close() {
|
|
497
|
+
if (this.edited.size === 0 && this.capChanges.length === 0) {
|
|
498
|
+
this.onCancel?.();
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
this.onSubmit?.({ edited: [...this.edited], capChanges: [...this.capChanges] });
|
|
502
|
+
}
|
|
503
|
+
// modals
|
|
504
|
+
handleModal(data) {
|
|
505
|
+
const modal = this.modal;
|
|
506
|
+
if (modal === undefined)
|
|
507
|
+
return;
|
|
508
|
+
if (matchesKey(data, 'escape')) {
|
|
509
|
+
this.modal = undefined;
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (modal.kind === 'choice') {
|
|
513
|
+
if (this.isDown(data))
|
|
514
|
+
modal.index = Math.min(modal.options.length - 1, modal.index + 1);
|
|
515
|
+
else if (this.isUp(data))
|
|
516
|
+
modal.index = Math.max(0, modal.index - 1);
|
|
517
|
+
else if (matchesKey(data, 'enter'))
|
|
518
|
+
modal.submit(modal.options[modal.index]);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (modal.kind === 'confirm') {
|
|
522
|
+
if (matchesKey(data, 'y') || matchesKey(data, 'enter')) {
|
|
523
|
+
this.modal = undefined;
|
|
524
|
+
modal.submit();
|
|
525
|
+
}
|
|
526
|
+
else if (matchesKey(data, 'n'))
|
|
527
|
+
this.modal = undefined;
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (matchesKey(data, 'enter'))
|
|
531
|
+
modal.submit(modal.input.getValue());
|
|
532
|
+
else
|
|
533
|
+
modal.input.handleInput(data);
|
|
534
|
+
}
|
|
535
|
+
renderModal(width, height) {
|
|
536
|
+
const modal = this.modal;
|
|
537
|
+
if (modal === undefined)
|
|
538
|
+
return [];
|
|
539
|
+
let lines;
|
|
540
|
+
if (modal.kind === 'choice') {
|
|
541
|
+
lines = [
|
|
542
|
+
theme.fg('accent', theme.bold(modal.title)),
|
|
543
|
+
'',
|
|
544
|
+
...modal.options.map((option, index) => `${index === modal.index ? theme.fg('accent', '▶') : ' '} ${option}`),
|
|
545
|
+
];
|
|
546
|
+
}
|
|
547
|
+
else if (modal.kind === 'confirm') {
|
|
548
|
+
lines = [theme.fg('warning', theme.bold(modal.title)), '', ...wrap(modal.message, Math.max(1, width - 6))];
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
lines = [
|
|
552
|
+
theme.fg('accent', theme.bold(modal.title)),
|
|
553
|
+
theme.fg('dim', modal.hint),
|
|
554
|
+
'',
|
|
555
|
+
modal.input.render(Math.max(1, width - 6))[0] ?? '',
|
|
556
|
+
];
|
|
557
|
+
}
|
|
558
|
+
const out = framed(lines, width);
|
|
559
|
+
while (out.length < height)
|
|
560
|
+
out.push('');
|
|
561
|
+
return out.slice(0, height);
|
|
562
|
+
}
|
|
563
|
+
// rendering
|
|
564
|
+
footer(width) {
|
|
565
|
+
if (this.busy !== undefined)
|
|
566
|
+
return theme.fg('warning', this.busy);
|
|
567
|
+
if (this.modal?.kind === 'choice')
|
|
568
|
+
return theme.fg('dim', 'j/k choose · enter apply · esc cancel');
|
|
569
|
+
if (this.modal?.kind === 'confirm')
|
|
570
|
+
return theme.fg('dim', 'y/enter confirm · n/esc cancel');
|
|
571
|
+
if (this.modal?.kind === 'field')
|
|
572
|
+
return theme.fg('dim', 'type · enter apply · esc cancel');
|
|
573
|
+
if (this.docs.ownsKeyboard)
|
|
574
|
+
return theme.fg('dim', 'type to filter · enter keep · esc clear');
|
|
575
|
+
if (this.notice !== undefined)
|
|
576
|
+
return theme.fg(this.notice.ok ? 'success' : 'warning', truncateToWidth(this.notice.text, width));
|
|
577
|
+
const move = `${this.label('crtr.setup.list-down', 'j')}/${this.label('crtr.setup.list-up', 'k')}`;
|
|
578
|
+
const left = this.zone === 'axes'
|
|
579
|
+
? `${move} axis · space change · enter documents`
|
|
580
|
+
: this.zone === 'docs'
|
|
581
|
+
? `${move} doc · / filter · d ${this.docs.filteringDelivering ? 'all' : 'delivering'} · enter dossier`
|
|
582
|
+
: `${move} field · enter edit · e add · x remove · g gate · b body · u unlisted · s slash · m cap · r rename`;
|
|
583
|
+
const right = `${this.label('crtr.setup.tab-next', 'tab')} event · ${this.label('crtr.setup.cancel', 'q')} close`;
|
|
584
|
+
const gap = width - visibleWidth(left) - visibleWidth(right);
|
|
585
|
+
return gap >= 2
|
|
586
|
+
? `${theme.fg('dim', left)}${' '.repeat(gap)}${theme.fg('dim', right)}`
|
|
587
|
+
: theme.fg('dim', truncateToWidth(`${left} · ${this.label('crtr.setup.cancel', 'q')} close`, width));
|
|
588
|
+
}
|
|
589
|
+
render(width) {
|
|
590
|
+
const height = this.terminalRows();
|
|
591
|
+
const bodyHeight = Math.max(1, height - PAGE_CHROME_ROWS);
|
|
592
|
+
const railVisible = width >= RAIL_MIN_TOTAL_WIDTH;
|
|
593
|
+
const axes = axesOf(this.snapshot, profileNameFor(this.snapshot.profileId) ?? 'none');
|
|
594
|
+
this.axisIndex = Math.max(0, Math.min(this.axisIndex, axes.length - 1));
|
|
595
|
+
let body;
|
|
596
|
+
if (width >= THREE_COLUMN_MIN_WIDTH) {
|
|
597
|
+
const detailWidth = Math.max(1, width - RAIL_WIDTH - DOCS_WIDTH - 6);
|
|
598
|
+
const middle = columns(this.docs.render(DOCS_WIDTH, bodyHeight, this.zone === 'docs', this.plans), DOCS_WIDTH, this.modal === undefined
|
|
599
|
+
? this.detail.render(detailWidth, bodyHeight, this.zone === 'detail')
|
|
600
|
+
: this.renderModal(detailWidth, bodyHeight), detailWidth);
|
|
601
|
+
body = columns(renderAxes(axes, this.axisIndex, RAIL_WIDTH, bodyHeight, this.zone === 'axes'), RAIL_WIDTH, middle, width - RAIL_WIDTH - 3);
|
|
602
|
+
}
|
|
603
|
+
else if (railVisible) {
|
|
604
|
+
const panelWidth = width - RAIL_WIDTH - 3;
|
|
605
|
+
const panel = this.modal !== undefined
|
|
606
|
+
? this.renderModal(panelWidth, bodyHeight)
|
|
607
|
+
: this.zone === 'detail'
|
|
608
|
+
? this.detail.render(panelWidth, bodyHeight, true)
|
|
609
|
+
: this.docs.render(panelWidth, bodyHeight, this.zone === 'docs', this.plans);
|
|
610
|
+
body = columns(renderAxes(axes, this.axisIndex, RAIL_WIDTH, bodyHeight, this.zone === 'axes'), RAIL_WIDTH, panel, panelWidth);
|
|
611
|
+
}
|
|
612
|
+
else if (this.modal !== undefined) {
|
|
613
|
+
body = this.renderModal(width, bodyHeight);
|
|
614
|
+
}
|
|
615
|
+
else if (this.zone === 'axes') {
|
|
616
|
+
body = renderAxes(axes, this.axisIndex, width, bodyHeight, true);
|
|
617
|
+
}
|
|
618
|
+
else if (this.zone === 'detail') {
|
|
619
|
+
body = this.detail.render(width, bodyHeight, true);
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
body = this.docs.render(width, bodyHeight, true, this.plans);
|
|
623
|
+
}
|
|
624
|
+
const pending = SURFACE_EVENTS.filter((event) => this.plans.peek(event) === undefined).length;
|
|
625
|
+
const meta = [
|
|
626
|
+
this.snapshot.event,
|
|
627
|
+
pending > 0 ? `${plural(pending, 'event', 'events')} planning` : 'all events planned',
|
|
628
|
+
this.edited.size > 0 ? `${plural(this.edited.size, 'doc', 'docs')} edited` : '',
|
|
629
|
+
]
|
|
630
|
+
.filter((part) => part !== '')
|
|
631
|
+
.join(' · ');
|
|
632
|
+
return pageFrame({ width, height, title: 'crouter context', meta, body, footer: this.footer(width) });
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/** Every field here is an edit-or-accept, never a blank to retype, so the
|
|
636
|
+
* cursor lands at the end (`\x05`) — `setValue` alone leaves it at 0, where
|
|
637
|
+
* typing prepends and backspace does nothing. */
|
|
638
|
+
function seeded(value) {
|
|
639
|
+
const input = new Input();
|
|
640
|
+
input.setValue(value);
|
|
641
|
+
input.handleInput('\x05');
|
|
642
|
+
input.focused = true;
|
|
643
|
+
return input;
|
|
644
|
+
}
|
|
645
|
+
function wrap(text, width) {
|
|
646
|
+
const out = [];
|
|
647
|
+
let line = '';
|
|
648
|
+
for (const word of text.trim().split(/\s+/)) {
|
|
649
|
+
const next = line === '' ? word : `${line} ${word}`;
|
|
650
|
+
if (visibleWidth(next) <= width)
|
|
651
|
+
line = next;
|
|
652
|
+
else {
|
|
653
|
+
if (line !== '')
|
|
654
|
+
out.push(line);
|
|
655
|
+
line = word;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (line !== '')
|
|
659
|
+
out.push(line);
|
|
660
|
+
return out;
|
|
661
|
+
}
|
|
662
|
+
export async function runContextAdmin(seed) {
|
|
663
|
+
return runTuiPage(({ tui, terminal, bindings, suspendForChild }, control) => {
|
|
664
|
+
const shell = new ContextAdminShell(seed, tui, () => terminal.rows, bindings, suspendForChild);
|
|
665
|
+
shell.onCancel = control.cancel;
|
|
666
|
+
shell.onSubmit = control.submit;
|
|
667
|
+
return shell;
|
|
668
|
+
});
|
|
669
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const adminLeaf: import("../../../core/command.js").LeafDef;
|