@gaia-ai/addon-gaia-ui 0.9.2 → 0.11.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/dist/src/Component/footer-actions/index.d.ts +58 -0
- package/dist/src/Component/footer-actions/index.js +111 -0
- package/dist/src/Component/list-chrome/index.d.ts +19 -0
- package/dist/src/Component/list-chrome/index.js +61 -0
- package/dist/src/Component/markdown/index.d.ts +68 -0
- package/dist/src/Component/markdown/index.js +133 -0
- package/dist/src/Component/section-list/index.d.ts +115 -23
- package/dist/src/Component/section-list/index.js +120 -34
- package/dist/src/Component/top-tabs/index.d.ts +110 -7
- package/dist/src/Component/top-tabs/index.js +88 -9
- package/dist/src/Entity/collection.js +1 -1
- package/dist/src/Entity/conductor/Data/conductors.d.ts +4 -3
- package/dist/src/Entity/conductor/Data/conductors.js +76 -10
- package/dist/src/Entity/conductor/Data/own-conductor.js +7 -1
- package/dist/src/Entity/conductor/Screen/conductors.d.ts +26 -41
- package/dist/src/Entity/conductor/Screen/conductors.js +65 -123
- package/dist/src/Entity/project/Screen/project-picker.d.ts +20 -31
- package/dist/src/Entity/project/Screen/project-picker.js +48 -58
- package/dist/src/Entity/project/Screen/projects-screen.d.ts +4 -2
- package/dist/src/Entity/project/Screen/projects-screen.js +11 -8
- package/dist/src/Entity/run/Component/run-detail/index.js +15 -7
- package/dist/src/Entity/run/Data/query.d.ts +10 -0
- package/dist/src/Entity/run/Data/query.js +16 -1
- package/dist/src/Entity/run/Data/runs.d.ts +2 -0
- package/dist/src/Entity/run/Data/runs.js +19 -1
- package/dist/src/Entity/run/Screen/run-detail.d.ts +16 -23
- package/dist/src/Entity/run/Screen/run-detail.js +27 -34
- package/dist/src/Entity/run/Screen/runs.d.ts +7 -3
- package/dist/src/Entity/run/Screen/runs.js +31 -48
- package/dist/src/Entity/run/Screen/user-runs.d.ts +79 -0
- package/dist/src/Entity/run/Screen/user-runs.js +98 -0
- package/dist/src/Entity/statistics/Data/statistics.d.ts +111 -9
- package/dist/src/Entity/statistics/Data/statistics.js +121 -9
- package/dist/src/Entity/statistics/Screen/dashboard.d.ts +10 -2
- package/dist/src/Entity/statistics/Screen/dashboard.js +127 -36
- package/dist/src/Entity/team/Data/members.d.ts +30 -0
- package/dist/src/Entity/team/Data/members.js +83 -0
- package/dist/src/Entity/team/Data/team.d.ts +22 -0
- package/dist/src/Entity/team/Data/team.js +38 -0
- package/dist/src/Entity/team/Data/teams.d.ts +223 -0
- package/dist/src/Entity/team/Data/teams.js +267 -0
- package/dist/src/Entity/team/Screen/team-users.d.ts +76 -0
- package/dist/src/Entity/team/Screen/team-users.js +215 -0
- package/dist/src/Entity/team/Screen/teams-screen.d.ts +116 -0
- package/dist/src/Entity/team/Screen/teams-screen.js +349 -0
- package/dist/src/Entity/ticket/Component/comment-item/index.d.ts +14 -1
- package/dist/src/Entity/ticket/Component/comment-item/index.js +15 -1
- package/dist/src/Entity/ticket/Component/edit-form/index.d.ts +8 -3
- package/dist/src/Entity/ticket/Component/edit-form/index.js +4 -6
- package/dist/src/Entity/ticket/Component/sections/index.js +16 -1
- package/dist/src/Entity/ticket/Component/ticket-detail/index.d.ts +102 -13
- package/dist/src/Entity/ticket/Component/ticket-detail/index.js +440 -72
- package/dist/src/Entity/ticket/Data/detail.d.ts +19 -3
- package/dist/src/Entity/ticket/Data/detail.js +20 -4
- package/dist/src/Entity/ticket/Data/query.d.ts +7 -0
- package/dist/src/Entity/ticket/Data/query.js +18 -2
- package/dist/src/Entity/ticket/Data/tickets.d.ts +24 -0
- package/dist/src/Entity/ticket/Data/tickets.js +41 -1
- package/dist/src/Entity/ticket/Screen/create-ticket-form.d.ts +10 -4
- package/dist/src/Entity/ticket/Screen/create-ticket-form.js +37 -16
- package/dist/src/Entity/ticket/Screen/edit-assignment-form.d.ts +6 -4
- package/dist/src/Entity/ticket/Screen/edit-assignment-form.js +16 -16
- package/dist/src/Entity/ticket/Screen/parent-filter-form.d.ts +6 -4
- package/dist/src/Entity/ticket/Screen/parent-filter-form.js +14 -11
- package/dist/src/Entity/ticket/Screen/ticket-agents.d.ts +5 -2
- package/dist/src/Entity/ticket/Screen/ticket-agents.js +14 -14
- package/dist/src/Entity/ticket/Screen/ticket-detail.d.ts +89 -4
- package/dist/src/Entity/ticket/Screen/ticket-detail.js +295 -74
- package/dist/src/Entity/ticket/Screen/tickets.d.ts +66 -3
- package/dist/src/Entity/ticket/Screen/tickets.js +140 -76
- package/dist/src/Entity/ticket/Screen/user-tickets.d.ts +91 -0
- package/dist/src/Entity/ticket/Screen/user-tickets.js +94 -0
- package/dist/src/Entity/ticket/Screen/who-filter-form.d.ts +6 -4
- package/dist/src/Entity/ticket/Screen/who-filter-form.js +14 -11
- package/dist/src/Entity/transport.d.ts +14 -4
- package/dist/src/Entity/transport.js +2 -1
- package/dist/src/Entity/user/Data/users.d.ts +87 -0
- package/dist/src/Entity/user/Data/users.js +60 -0
- package/dist/src/Entity/user/Screen/user-overview.d.ts +33 -0
- package/dist/src/Entity/user/Screen/user-overview.js +159 -0
- package/dist/src/Entity/user/Screen/users.d.ts +39 -0
- package/dist/src/Entity/user/Screen/users.js +136 -0
- package/dist/src/Extension/contract.d.ts +58 -0
- package/dist/src/Extension/contract.js +1 -0
- package/dist/src/Extension/load.d.ts +10 -0
- package/dist/src/Extension/load.js +21 -0
- package/dist/src/Extension/store.d.ts +11 -0
- package/dist/src/Extension/store.js +44 -0
- package/dist/src/Kernel/chrome.d.ts +49 -12
- package/dist/src/Kernel/chrome.js +150 -57
- package/dist/src/Kernel/key-input.js +1 -1
- package/dist/src/Kernel/mount.d.ts +1 -1
- package/dist/src/Kernel/mount.js +1 -1
- package/dist/src/Kernel/regions.d.ts +64 -0
- package/dist/src/Kernel/regions.js +328 -0
- package/dist/src/Kernel/renderer.d.ts +25 -0
- package/dist/src/Kernel/renderer.js +142 -0
- package/dist/src/Kernel/splash.d.ts +27 -0
- package/dist/src/Kernel/splash.js +72 -0
- package/dist/src/Kernel/tui-kernel.js +139 -435
- package/dist/src/Routing/context.d.ts +5 -0
- package/dist/src/Routing/context.js +6 -0
- package/dist/src/Routing/registry.js +21 -2
- package/dist/src/Routing/router.js +5 -1
- package/dist/src/Routing/routes.js +19 -0
- package/dist/src/Screen/async-modal-screen.d.ts +26 -0
- package/dist/src/Screen/async-modal-screen.js +44 -0
- package/dist/src/Screen/async-screen.d.ts +32 -40
- package/dist/src/Screen/async-screen.js +45 -41
- package/dist/src/Screen/full-screen.d.ts +1 -0
- package/dist/src/Screen/full-screen.js +3 -0
- package/dist/src/Screen/list-screen.d.ts +7 -1
- package/dist/src/Screen/list-screen.js +17 -6
- package/dist/src/Screen/modal-screen.d.ts +6 -0
- package/dist/src/Screen/modal-screen.js +2 -0
- package/dist/src/Screen/screen.d.ts +42 -6
- package/dist/src/Screen/screen.js +2 -2
- package/dist/src/Widget/textarea.js +10 -14
- package/dist/src/Widget/types.d.ts +1 -1
- package/dist/src/launcher.d.ts +24 -26
- package/dist/src/launcher.js +11 -11
- package/dist/src/lib/format.js +1 -1
- package/dist/src/lib/markdown.d.ts +53 -0
- package/dist/src/lib/markdown.js +522 -0
- package/dist/src/lib/refresh-signal.d.ts +56 -0
- package/dist/src/lib/refresh-signal.js +85 -0
- package/dist/src/lib/theme.d.ts +36 -0
- package/dist/src/lib/theme.js +40 -0
- package/dist/src/lib/version.d.ts +5 -0
- package/dist/src/lib/version.js +46 -0
- package/dist/src/lib/vnode.d.ts +86 -63
- package/dist/src/lib/vnode.js +112 -85
- package/dist/src/types.d.ts +47 -12
- package/dist/src/ui.d.ts +2 -0
- package/dist/src/ui.js +1 -0
- package/package.json +4 -3
|
@@ -49,7 +49,13 @@ export function selectOwnConductor(doc, gaiaDir) {
|
|
|
49
49
|
? str((doc.included ?? []).find((inc) => inc.id === projectId)?.attributes
|
|
50
50
|
?.name)
|
|
51
51
|
: undefined;
|
|
52
|
-
|
|
52
|
+
// GAIA-353 renamed the registration key machine_id → conductor_id; accept
|
|
53
|
+
// the legacy attribute during cutover so a pre-upgrade control plane still
|
|
54
|
+
// preselects this checkout's conductor.
|
|
55
|
+
return {
|
|
56
|
+
machineId: str(own.attributes?.conductor_id) ?? str(own.attributes?.machine_id),
|
|
57
|
+
project,
|
|
58
|
+
};
|
|
53
59
|
}
|
|
54
60
|
/**
|
|
55
61
|
* Read the conductor collection and resolve this checkout's own record.
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type { VNode } from '@opentui/core';
|
|
2
|
+
import { AsyncScreen } from '../../../Screen/async-screen.js';
|
|
3
|
+
import type { FullScreenChrome } from '../../../Screen/full-screen.js';
|
|
4
|
+
import type { ScreenContext } from '../../../Screen/screen.js';
|
|
5
|
+
import { type ConductorRow } from '../Data/conductors.js';
|
|
6
|
+
/** Loaded conductors page — rows plus project-name lookup for the table. */
|
|
7
|
+
interface ConductorsData {
|
|
8
|
+
readonly rows: readonly ConductorRow[];
|
|
9
|
+
readonly projectNames: ReadonlyMap<string, string>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Conductors list — a normal {@link AsyncScreen}.
|
|
13
|
+
*
|
|
14
|
+
* Refresh is the shared mechanism (GAIA-376 `ui-refresh` file + `R`), not a
|
|
15
|
+
* screen-private timer. Enter loads; navigation away cancels in-flight reads
|
|
16
|
+
* via the async lifecycle.
|
|
17
|
+
*/
|
|
18
|
+
export declare class ConductorsScreen extends AsyncScreen<ConductorsData> {
|
|
3
19
|
static readonly route: {
|
|
4
20
|
id: string;
|
|
5
21
|
path: string;
|
|
@@ -12,56 +28,25 @@ export declare class ConductorsScreen extends BaseScreen {
|
|
|
12
28
|
};
|
|
13
29
|
readonly id: string;
|
|
14
30
|
readonly frameId = "conductors";
|
|
15
|
-
private rows;
|
|
16
|
-
private projectNames;
|
|
17
31
|
private selectedIndex;
|
|
18
32
|
private logFor;
|
|
19
|
-
private status;
|
|
20
|
-
private reading;
|
|
21
|
-
private timer;
|
|
22
33
|
private readonly busyIds;
|
|
23
|
-
private activation;
|
|
24
34
|
topTab(): {
|
|
25
35
|
readonly level: "root";
|
|
26
36
|
readonly tab: "conductors";
|
|
27
37
|
};
|
|
28
|
-
private quietRead;
|
|
29
|
-
get busy(): boolean;
|
|
30
38
|
reload(context: ScreenContext): void;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
* noun and the empty message are `CONDUCTOR_TABLE`, declared beside the row
|
|
39
|
-
* mapper that produces the rows. What this screen still owns is the CONTEXT
|
|
40
|
-
* the columns read — the frame's clock, which conductors have an action in
|
|
41
|
-
* flight, and the project names its own small read resolved — because none of
|
|
42
|
-
* those is a property of a `gaia_conductor` record.
|
|
43
|
-
*
|
|
44
|
-
* THE OPEN LOG IS NOT A ROW OF THE LIST, so it is not in the table: it is
|
|
45
|
-
* neither keyed on a conductor nor laid out in the columns, and a log line
|
|
46
|
-
* keyed under the list's ids could collide with a row's. It stays its own
|
|
47
|
-
* block below the table, whose lines are keyed by position — honest for a
|
|
48
|
-
* block replaced wholesale on every read that indexes nothing outside itself.
|
|
49
|
-
* The wrapper is stated on EVERY frame, log or no log, so the body's root
|
|
50
|
-
* node keeps one identity across the keypress that opens one.
|
|
51
|
-
*
|
|
52
|
-
* MOUNTED WHOLE, so the bound on its nodes is the data's own: one box of 8
|
|
53
|
-
* cells per conductor, over the conductors of one user on one control plane,
|
|
54
|
-
* plus at most 40 log lines. `@opentui/core@0.5.1` hands out 16 382 before it
|
|
55
|
-
* throws, so this screen windows nothing of its own — deliberately, not by
|
|
56
|
-
* omission.
|
|
57
|
-
*/
|
|
58
|
-
private surface;
|
|
59
|
-
present(context: ScreenContext): import("../../../Screen/screen.js").ScreenPresentation;
|
|
39
|
+
get rows(): readonly ConductorRow[];
|
|
40
|
+
protected load(context: ScreenContext): Promise<ConductorsData>;
|
|
41
|
+
protected renderLoading(): string[];
|
|
42
|
+
protected renderReady(data: ConductorsData, context: ScreenContext): VNode;
|
|
43
|
+
protected didLoad(data: ConductorsData): void;
|
|
44
|
+
protected chrome(_context: ScreenContext): FullScreenChrome;
|
|
45
|
+
protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
|
|
60
46
|
select(index: number, context: ScreenContext): boolean;
|
|
61
47
|
activate(index: number, context: ScreenContext): boolean;
|
|
62
48
|
handleInput(input: string, context: ScreenContext): boolean;
|
|
63
49
|
reset(): void;
|
|
64
|
-
private refresh;
|
|
65
50
|
private act;
|
|
66
|
-
private stopTimer;
|
|
67
51
|
}
|
|
52
|
+
export {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
+
import { actionItem } from '../../../Component/footer-actions/index.js';
|
|
2
3
|
import { columnNode, textNode } from '../../../lib/vnode.js';
|
|
3
|
-
import {
|
|
4
|
+
import { AsyncScreen } from '../../../Screen/async-screen.js';
|
|
4
5
|
import { listRowIds, listTableTree } from '../../collection.js';
|
|
5
6
|
import { CONDUCTOR_TABLE, listConductorRows, listProjectNames, } from '../Data/conductors.js';
|
|
6
7
|
import { tailConductorLog } from '../Data/log-tail.js';
|
|
7
8
|
import { createConductorProcessHost } from '../Data/process.js';
|
|
8
|
-
const REFRESH_MS = 5_000;
|
|
9
9
|
/**
|
|
10
10
|
* The open row's log block: where it is, and its tail.
|
|
11
11
|
*
|
|
@@ -24,7 +24,14 @@ const logFor = (row) => row.isLocal
|
|
|
24
24
|
heading: `log — ${row.label} runs on another machine; no log here`,
|
|
25
25
|
lines: [],
|
|
26
26
|
};
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Conductors list — a normal {@link AsyncScreen}.
|
|
29
|
+
*
|
|
30
|
+
* Refresh is the shared mechanism (GAIA-376 `ui-refresh` file + `R`), not a
|
|
31
|
+
* screen-private timer. Enter loads; navigation away cancels in-flight reads
|
|
32
|
+
* via the async lifecycle.
|
|
33
|
+
*/
|
|
34
|
+
export class ConductorsScreen extends AsyncScreen {
|
|
28
35
|
static route = {
|
|
29
36
|
id: 'conductors',
|
|
30
37
|
path: '/conductors',
|
|
@@ -32,70 +39,41 @@ export class ConductorsScreen extends BaseScreen {
|
|
|
32
39
|
};
|
|
33
40
|
id = ConductorsScreen.route.id;
|
|
34
41
|
frameId = 'conductors';
|
|
35
|
-
rows = [];
|
|
36
|
-
projectNames = new Map();
|
|
37
42
|
selectedIndex = 0;
|
|
38
43
|
logFor = null;
|
|
39
|
-
status = '';
|
|
40
|
-
reading = false;
|
|
41
|
-
timer = null;
|
|
42
44
|
busyIds = new Set();
|
|
43
|
-
activation = 0;
|
|
44
45
|
topTab() {
|
|
45
46
|
return { level: 'root', tab: 'conductors' };
|
|
46
47
|
}
|
|
47
|
-
// GAIA-285 spec D5/D6 — the shared `R` lands here; the 5 s tick passes
|
|
48
|
-
// `quiet` so only an EXPLICIT reload states its loading in the footer.
|
|
49
|
-
quietRead = false;
|
|
50
|
-
get busy() {
|
|
51
|
-
return this.reading && !this.quietRead;
|
|
52
|
-
}
|
|
53
48
|
reload(context) {
|
|
54
|
-
|
|
49
|
+
this.retry(context);
|
|
55
50
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
51
|
+
get rows() {
|
|
52
|
+
return this.readyValue?.rows ?? [];
|
|
53
|
+
}
|
|
54
|
+
async load(context) {
|
|
55
|
+
if (!context.services)
|
|
56
|
+
throw new Error('Conductors requires services');
|
|
57
|
+
const client = context.services.client;
|
|
58
|
+
const [projectNames, rows] = await Promise.all([
|
|
59
|
+
listProjectNames(client),
|
|
60
|
+
listConductorRows(client, {
|
|
61
|
+
now: context.nowSeconds?.() ?? Math.floor(Date.now() / 1000),
|
|
62
|
+
exists: existsSync,
|
|
63
|
+
}),
|
|
64
|
+
]);
|
|
65
|
+
return { rows, projectNames };
|
|
62
66
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.stopTimer();
|
|
67
|
+
renderLoading() {
|
|
68
|
+
return ['Loading conductors…'];
|
|
66
69
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
* since GAIA-345 that tree is the SHARED table's (AC 1/2).
|
|
70
|
-
*
|
|
71
|
-
* There is no `conductor-list` component any more: the eight columns, the
|
|
72
|
-
* noun and the empty message are `CONDUCTOR_TABLE`, declared beside the row
|
|
73
|
-
* mapper that produces the rows. What this screen still owns is the CONTEXT
|
|
74
|
-
* the columns read — the frame's clock, which conductors have an action in
|
|
75
|
-
* flight, and the project names its own small read resolved — because none of
|
|
76
|
-
* those is a property of a `gaia_conductor` record.
|
|
77
|
-
*
|
|
78
|
-
* THE OPEN LOG IS NOT A ROW OF THE LIST, so it is not in the table: it is
|
|
79
|
-
* neither keyed on a conductor nor laid out in the columns, and a log line
|
|
80
|
-
* keyed under the list's ids could collide with a row's. It stays its own
|
|
81
|
-
* block below the table, whose lines are keyed by position — honest for a
|
|
82
|
-
* block replaced wholesale on every read that indexes nothing outside itself.
|
|
83
|
-
* The wrapper is stated on EVERY frame, log or no log, so the body's root
|
|
84
|
-
* node keeps one identity across the keypress that opens one.
|
|
85
|
-
*
|
|
86
|
-
* MOUNTED WHOLE, so the bound on its nodes is the data's own: one box of 8
|
|
87
|
-
* cells per conductor, over the conductors of one user on one control plane,
|
|
88
|
-
* plus at most 40 log lines. `@opentui/core@0.5.1` hands out 16 382 before it
|
|
89
|
-
* throws, so this screen windows nothing of its own — deliberately, not by
|
|
90
|
-
* omission.
|
|
91
|
-
*/
|
|
92
|
-
surface(context) {
|
|
93
|
-
const open = this.rows.find((row) => row.id === this.logFor);
|
|
70
|
+
renderReady(data, context) {
|
|
71
|
+
const open = data.rows.find((row) => row.id === this.logFor);
|
|
94
72
|
const log = open ? logFor(open) : undefined;
|
|
95
|
-
return columnNode('conductor-list', listTableTree(CONDUCTOR_TABLE,
|
|
73
|
+
return columnNode('conductor-list', listTableTree(CONDUCTOR_TABLE, data.rows, {
|
|
96
74
|
nowSeconds: context.nowSeconds?.() ?? Math.floor(Date.now() / 1000),
|
|
97
75
|
busyIds: this.busyIds,
|
|
98
|
-
projectNames:
|
|
76
|
+
projectNames: data.projectNames,
|
|
99
77
|
}, { selectedIndex: this.selectedIndex }), ...(log
|
|
100
78
|
? [
|
|
101
79
|
textNode({ id: 'conductor-log-gap', content: '' }),
|
|
@@ -104,19 +82,26 @@ export class ConductorsScreen extends BaseScreen {
|
|
|
104
82
|
]
|
|
105
83
|
: []));
|
|
106
84
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
85
|
+
didLoad(data) {
|
|
86
|
+
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, data.rows.length - 1));
|
|
87
|
+
}
|
|
88
|
+
chrome(_context) {
|
|
89
|
+
const rows = this.rows;
|
|
90
|
+
return {
|
|
91
|
+
selectable: rows.length,
|
|
110
92
|
selectedIndex: this.selectedIndex,
|
|
111
|
-
|
|
112
|
-
// SAME configuration that keys the rows — so the click target and the row
|
|
113
|
-
// node cannot be spelled two ways (GAIA-345 D2). The log's lines are not
|
|
114
|
-
// in here, which is the other half of "a log is not a list row".
|
|
115
|
-
rowIds: listRowIds(CONDUCTOR_TABLE, this.rows),
|
|
93
|
+
rowIds: listRowIds(CONDUCTOR_TABLE, rows),
|
|
116
94
|
title: 'GAIA conductors',
|
|
117
|
-
meta: `${
|
|
118
|
-
|
|
119
|
-
|
|
95
|
+
meta: `${rows.length} conductor(s)`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
actions() {
|
|
99
|
+
return [
|
|
100
|
+
actionItem('conductors-start', 's', 'start'),
|
|
101
|
+
actionItem('conductors-stop', 'x', 'stop'),
|
|
102
|
+
actionItem('conductors-enter', 'Enter', 'log'),
|
|
103
|
+
actionItem('conductors-tab', 'Tab', 'tabs'),
|
|
104
|
+
];
|
|
120
105
|
}
|
|
121
106
|
select(index, context) {
|
|
122
107
|
if (index < 0 || index >= this.rows.length)
|
|
@@ -134,6 +119,8 @@ export class ConductorsScreen extends BaseScreen {
|
|
|
134
119
|
return true;
|
|
135
120
|
}
|
|
136
121
|
handleInput(input, context) {
|
|
122
|
+
if (super.handleInput(input, context))
|
|
123
|
+
return true;
|
|
137
124
|
if (input === 's' || input === 'x') {
|
|
138
125
|
void this.act(input === 's' ? 'start' : 'stop', context);
|
|
139
126
|
return true;
|
|
@@ -147,87 +134,42 @@ export class ConductorsScreen extends BaseScreen {
|
|
|
147
134
|
return false;
|
|
148
135
|
}
|
|
149
136
|
reset() {
|
|
150
|
-
this.leave();
|
|
151
|
-
this.rows = [];
|
|
152
|
-
this.projectNames.clear();
|
|
153
137
|
this.selectedIndex = 0;
|
|
154
138
|
this.logFor = null;
|
|
155
|
-
this.status = '';
|
|
156
139
|
this.busyIds.clear();
|
|
157
|
-
|
|
158
|
-
async refresh(context, includeProjects = false, activation = this.activation, quiet = false) {
|
|
159
|
-
if (this.reading || !context.services)
|
|
160
|
-
return;
|
|
161
|
-
this.reading = true;
|
|
162
|
-
this.quietRead = quiet;
|
|
163
|
-
try {
|
|
164
|
-
if (includeProjects && this.projectNames.size === 0) {
|
|
165
|
-
const names = await listProjectNames(context.services.client);
|
|
166
|
-
if (activation !== this.activation)
|
|
167
|
-
return;
|
|
168
|
-
this.projectNames = names;
|
|
169
|
-
}
|
|
170
|
-
const rows = await listConductorRows(context.services.client, {
|
|
171
|
-
now: context.nowSeconds?.() ?? Math.floor(Date.now() / 1000),
|
|
172
|
-
exists: existsSync,
|
|
173
|
-
});
|
|
174
|
-
if (activation !== this.activation)
|
|
175
|
-
return;
|
|
176
|
-
this.rows = rows;
|
|
177
|
-
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.rows.length - 1));
|
|
178
|
-
}
|
|
179
|
-
finally {
|
|
180
|
-
this.reading = false;
|
|
181
|
-
this.quietRead = false;
|
|
182
|
-
}
|
|
183
|
-
// After the finally, so the frame this triggers already reads busy=false —
|
|
184
|
-
// drawn from inside the try it would restate `Loading…` one last time.
|
|
185
|
-
if (context.isActive())
|
|
186
|
-
context.requestDraw();
|
|
140
|
+
super.reset();
|
|
187
141
|
}
|
|
188
142
|
async act(action, context) {
|
|
189
143
|
const row = this.rows[this.selectedIndex];
|
|
190
144
|
if (!row || this.busyIds.has(row.id))
|
|
191
145
|
return;
|
|
192
|
-
if (!row.isLocal)
|
|
193
|
-
this.status = `${row.label} runs on another machine — no action from here`;
|
|
194
|
-
context.requestDraw();
|
|
146
|
+
if (!row.isLocal)
|
|
195
147
|
return;
|
|
196
|
-
|
|
197
|
-
if (action === 'start' && row.derivedStatus === 'running') {
|
|
198
|
-
this.status = `${row.label} is already running`;
|
|
199
|
-
context.requestDraw();
|
|
148
|
+
if (action === 'start' && row.derivedStatus === 'running')
|
|
200
149
|
return;
|
|
201
|
-
}
|
|
202
150
|
this.busyIds.add(row.id);
|
|
203
|
-
this.status = `${action}…`;
|
|
204
151
|
context.requestDraw();
|
|
205
152
|
const host = context.services?.conductorProcess ?? createConductorProcessHost();
|
|
206
153
|
try {
|
|
207
|
-
|
|
208
|
-
|
|
154
|
+
if (action === 'start') {
|
|
155
|
+
await host.start({
|
|
209
156
|
workspaceRoot: row.workspaceRoot,
|
|
210
157
|
conductorName: row.conductorName,
|
|
211
|
-
})
|
|
212
|
-
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
await host.stop({
|
|
213
162
|
pid: row.lastPid,
|
|
214
163
|
workspaceRoot: row.workspaceRoot,
|
|
215
164
|
});
|
|
216
|
-
|
|
165
|
+
}
|
|
217
166
|
}
|
|
218
|
-
catch
|
|
219
|
-
|
|
167
|
+
catch {
|
|
168
|
+
// Outcome lives in the reloaded row state, not a footer notice.
|
|
220
169
|
}
|
|
221
170
|
finally {
|
|
222
171
|
this.busyIds.delete(row.id);
|
|
223
|
-
|
|
224
|
-
if (context.isActive())
|
|
225
|
-
context.requestDraw();
|
|
172
|
+
this.retry(context);
|
|
226
173
|
}
|
|
227
174
|
}
|
|
228
|
-
stopTimer() {
|
|
229
|
-
if (this.timer)
|
|
230
|
-
clearInterval(this.timer);
|
|
231
|
-
this.timer = null;
|
|
232
|
-
}
|
|
233
175
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { VNode } from '@opentui/core';
|
|
2
|
+
import { AsyncScreen } from '../../../Screen/async-screen.js';
|
|
3
|
+
import type { FullScreenChrome } from '../../../Screen/full-screen.js';
|
|
4
|
+
import type { ScreenContext } from '../../../Screen/screen.js';
|
|
2
5
|
/**
|
|
3
6
|
* A project that claims the repo.
|
|
4
7
|
*
|
|
@@ -15,7 +18,11 @@ export interface ProjectPickerInput {
|
|
|
15
18
|
readonly repo: string;
|
|
16
19
|
}
|
|
17
20
|
export declare const projectPickerInputContext: import("../../../Routing/context.js").ContextKey<ProjectPickerInput>;
|
|
18
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Project picker — normal {@link AsyncScreen}. Candidates arrive via navigation
|
|
23
|
+
* context; refresh is the shared R / ui-refresh path.
|
|
24
|
+
*/
|
|
25
|
+
export declare class ProjectPickerScreen extends AsyncScreen<ProjectPickerInput> {
|
|
19
26
|
readonly frameId = "project-picker";
|
|
20
27
|
topTab(): {
|
|
21
28
|
readonly level: "root";
|
|
@@ -31,48 +38,30 @@ export declare class ProjectPickerScreen extends BaseScreen {
|
|
|
31
38
|
context: (_params: Readonly<Record<string, string>>, state: unknown, base: import("../../../Routing/context.js").ContextBag) => import("../../../Routing/context.js").ContextBag;
|
|
32
39
|
};
|
|
33
40
|
readonly id: string;
|
|
34
|
-
private candidates;
|
|
35
|
-
private repo;
|
|
36
41
|
selectedIndex: number;
|
|
37
|
-
|
|
42
|
+
reload(context: ScreenContext): void;
|
|
43
|
+
get candidates(): readonly PickerCandidate[];
|
|
44
|
+
get repo(): string;
|
|
38
45
|
get rows(): readonly PickerCandidate[];
|
|
46
|
+
protected load(context: ScreenContext): Promise<ProjectPickerInput>;
|
|
47
|
+
protected renderLoading(): string[];
|
|
39
48
|
/**
|
|
40
49
|
* The question this screen asks, and the one place it is written.
|
|
41
50
|
*
|
|
42
|
-
* Both surfaces open with it — the tree
|
|
43
|
-
*
|
|
44
|
-
* disagree about the repo they name.
|
|
51
|
+
* Both surfaces open with it — the tree via meta, the pipe as its first line —
|
|
52
|
+
* so composing it twice is how the two would come to disagree about the repo.
|
|
45
53
|
*/
|
|
46
54
|
private headingText;
|
|
47
|
-
/** The candidate the cursor stands on, clamped: an out-of-range index must
|
|
48
|
-
* move the marker, never lose it. */
|
|
49
55
|
private selected;
|
|
56
|
+
protected renderReady(data: ProjectPickerInput): VNode;
|
|
50
57
|
/**
|
|
51
58
|
* F5 — the pipe's own output, and it stays `string[]`.
|
|
52
59
|
*
|
|
53
|
-
* GAIA-345 D7/D8 — built from the SAME configuration the tree is
|
|
54
|
-
* `listTableRows` and `lineText`, plus the two heading lines and the trailing
|
|
55
|
-
* hint, which are the screen's and not rows of the list. The bytes are
|
|
56
|
-
* unchanged by construction rather than by a comparison somebody has to
|
|
57
|
-
* remember to make, and `tests/Entity/project/Screen/project-picker.test.ts`
|
|
58
|
-
* pins them line for line against the pre-conversion capture.
|
|
60
|
+
* GAIA-345 D7/D8 — built from the SAME configuration the tree is.
|
|
59
61
|
*/
|
|
60
62
|
renderNonInteractive(): string[];
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
*
|
|
64
|
-
* The two heading rows are composed AROUND the table's node because they are
|
|
65
|
-
* not rows of it — they name no candidate, they are not selectable, and they
|
|
66
|
-
* carry no entry in `rowIds`. That is also what the old `selRow` offset of 2
|
|
67
|
-
* was compensating for before the kernel started finding the cursor by node.
|
|
68
|
-
*
|
|
69
|
-
* MOUNTED WHOLE — no window of its own, and the bound is named rather than
|
|
70
|
-
* assumed: a row is one `gaia_project` that claims the cwd's remote, which is
|
|
71
|
-
* the multisite topology GAIA-126/137 designs for. That is a handful of sites
|
|
72
|
-
* per repo, against `@opentui/core@0.5.1`'s ceiling of 16 382 renderables.
|
|
73
|
-
*/
|
|
74
|
-
private surface;
|
|
75
|
-
present(_context: ScreenContext): import("../../../Screen/screen.js").ScreenPresentation;
|
|
63
|
+
protected chrome(_context: ScreenContext): FullScreenChrome;
|
|
64
|
+
protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
|
|
76
65
|
handleInput(input: string, context: ScreenContext): boolean;
|
|
77
66
|
activate(index: number, context: ScreenContext): boolean;
|
|
78
67
|
select(index: number, context: ScreenContext): boolean;
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
// GAIA-345 D8 — `Component/project-picker/` is GONE and its two survivors live
|
|
12
12
|
// here, in their only consumer: the candidate type and the one-column table the
|
|
13
13
|
// two surfaces are both made of.
|
|
14
|
+
import { actionItem } from '../../../Component/footer-actions/index.js';
|
|
14
15
|
import { lineText, } from '../../../Component/section-list/index.js';
|
|
15
|
-
import { columnNode, textNode } from '../../../lib/vnode.js';
|
|
16
16
|
import { defineContext } from '../../../Routing/context.js';
|
|
17
|
-
import {
|
|
17
|
+
import { AsyncScreen } from '../../../Screen/async-screen.js';
|
|
18
18
|
import { listRowIds, listTableRows, listTableTree, } from '../../collection.js';
|
|
19
19
|
/**
|
|
20
20
|
* The picker as a configuration (GAIA-345 D8) — one column, and NO HEADER.
|
|
@@ -54,7 +54,11 @@ const clamp = (selected, length) => {
|
|
|
54
54
|
return 0;
|
|
55
55
|
return Math.max(0, Math.min(Math.trunc(selected), length - 1));
|
|
56
56
|
};
|
|
57
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Project picker — normal {@link AsyncScreen}. Candidates arrive via navigation
|
|
59
|
+
* context; refresh is the shared R / ui-refresh path.
|
|
60
|
+
*/
|
|
61
|
+
export class ProjectPickerScreen extends AsyncScreen {
|
|
58
62
|
frameId = 'project-picker';
|
|
59
63
|
topTab() {
|
|
60
64
|
return { level: 'root', tab: 'projects' };
|
|
@@ -69,47 +73,53 @@ export class ProjectPickerScreen extends BaseScreen {
|
|
|
69
73
|
: base,
|
|
70
74
|
};
|
|
71
75
|
id = ProjectPickerScreen.route.id;
|
|
72
|
-
candidates = [];
|
|
73
|
-
repo = '';
|
|
74
76
|
selectedIndex = 0;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
context.requestDraw();
|
|
77
|
+
reload(context) {
|
|
78
|
+
this.retry(context);
|
|
79
|
+
}
|
|
80
|
+
get candidates() {
|
|
81
|
+
return this.readyValue?.candidates ?? [];
|
|
82
|
+
}
|
|
83
|
+
get repo() {
|
|
84
|
+
return this.readyValue?.repo ?? '';
|
|
85
85
|
}
|
|
86
86
|
get rows() {
|
|
87
87
|
return this.candidates;
|
|
88
88
|
}
|
|
89
|
+
load(context) {
|
|
90
|
+
if (context.contexts?.has(projectPickerInputContext)) {
|
|
91
|
+
const input = context.contexts.get(projectPickerInputContext);
|
|
92
|
+
return Promise.resolve({
|
|
93
|
+
candidates: [...input.candidates],
|
|
94
|
+
repo: input.repo,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return Promise.resolve({ candidates: [], repo: '' });
|
|
98
|
+
}
|
|
99
|
+
renderLoading() {
|
|
100
|
+
return ['Loading projects…'];
|
|
101
|
+
}
|
|
89
102
|
/**
|
|
90
103
|
* The question this screen asks, and the one place it is written.
|
|
91
104
|
*
|
|
92
|
-
* Both surfaces open with it — the tree
|
|
93
|
-
*
|
|
94
|
-
* disagree about the repo they name.
|
|
105
|
+
* Both surfaces open with it — the tree via meta, the pipe as its first line —
|
|
106
|
+
* so composing it twice is how the two would come to disagree about the repo.
|
|
95
107
|
*/
|
|
96
108
|
headingText() {
|
|
97
109
|
return `Several gaia projects claim ${this.repo} — which one?`;
|
|
98
110
|
}
|
|
99
|
-
/** The candidate the cursor stands on, clamped: an out-of-range index must
|
|
100
|
-
* move the marker, never lose it. */
|
|
101
111
|
selected() {
|
|
102
112
|
return this.candidates[clamp(this.selectedIndex, this.candidates.length)];
|
|
103
113
|
}
|
|
114
|
+
renderReady(data) {
|
|
115
|
+
return listTableTree(PICKER_TABLE, data.candidates, this.selected(), {
|
|
116
|
+
selectedIndex: clamp(this.selectedIndex, data.candidates.length),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
104
119
|
/**
|
|
105
120
|
* F5 — the pipe's own output, and it stays `string[]`.
|
|
106
121
|
*
|
|
107
|
-
* GAIA-345 D7/D8 — built from the SAME configuration the tree is
|
|
108
|
-
* `listTableRows` and `lineText`, plus the two heading lines and the trailing
|
|
109
|
-
* hint, which are the screen's and not rows of the list. The bytes are
|
|
110
|
-
* unchanged by construction rather than by a comparison somebody has to
|
|
111
|
-
* remember to make, and `tests/Entity/project/Screen/project-picker.test.ts`
|
|
112
|
-
* pins them line for line against the pre-conversion capture.
|
|
122
|
+
* GAIA-345 D7/D8 — built from the SAME configuration the tree is.
|
|
113
123
|
*/
|
|
114
124
|
renderNonInteractive() {
|
|
115
125
|
return [
|
|
@@ -120,46 +130,27 @@ export class ProjectPickerScreen extends BaseScreen {
|
|
|
120
130
|
'Run gaia ui project:<name> to choose directly.',
|
|
121
131
|
];
|
|
122
132
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
*
|
|
126
|
-
* The two heading rows are composed AROUND the table's node because they are
|
|
127
|
-
* not rows of it — they name no candidate, they are not selectable, and they
|
|
128
|
-
* carry no entry in `rowIds`. That is also what the old `selRow` offset of 2
|
|
129
|
-
* was compensating for before the kernel started finding the cursor by node.
|
|
130
|
-
*
|
|
131
|
-
* MOUNTED WHOLE — no window of its own, and the bound is named rather than
|
|
132
|
-
* assumed: a row is one `gaia_project` that claims the cwd's remote, which is
|
|
133
|
-
* the multisite topology GAIA-126/137 designs for. That is a handful of sites
|
|
134
|
-
* per repo, against `@opentui/core@0.5.1`'s ceiling of 16 382 renderables.
|
|
135
|
-
*/
|
|
136
|
-
surface() {
|
|
137
|
-
return columnNode('project-picker', textNode({
|
|
138
|
-
id: 'project-picker-heading',
|
|
139
|
-
content: this.headingText(),
|
|
140
|
-
}), textNode({ id: 'project-picker-heading-gap', content: '' }), listTableTree(PICKER_TABLE, this.candidates, this.selected(), {
|
|
141
|
-
selectedIndex: clamp(this.selectedIndex, this.candidates.length),
|
|
142
|
-
}));
|
|
143
|
-
}
|
|
144
|
-
present(_context) {
|
|
145
|
-
return presentation(this.surface(), {
|
|
133
|
+
chrome(_context) {
|
|
134
|
+
return {
|
|
146
135
|
selectable: this.candidates.length,
|
|
147
136
|
selectedIndex: this.selectedIndex,
|
|
148
|
-
// The node a click on each candidate lands on (GAIA-340 Task 11), read
|
|
149
|
-
// off the SAME configuration that keys the rows — which retires the
|
|
150
|
-
// hand-spelled `project-picker-candidate-<id>` the screen used to publish
|
|
151
|
-
// beside a row keyed the same way by a second expression (GAIA-345 D2).
|
|
152
137
|
rowIds: listRowIds(PICKER_TABLE, this.candidates),
|
|
153
138
|
title: 'Which project?',
|
|
154
|
-
meta: `${this.candidates.length} project(s) claim ${this.repo}
|
|
155
|
-
|
|
156
|
-
|
|
139
|
+
meta: `${this.candidates.length} project(s) claim ${this.repo} · ${this.headingText()}`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
actions() {
|
|
143
|
+
return [actionItem('picker-enter', 'Enter', 'open')];
|
|
157
144
|
}
|
|
158
145
|
handleInput(input, context) {
|
|
146
|
+
if (super.handleInput(input, context))
|
|
147
|
+
return true;
|
|
159
148
|
if (input === 'j' || input === 'down')
|
|
160
149
|
this.selectedIndex = Math.min(this.selectedIndex + 1, this.candidates.length - 1);
|
|
161
150
|
else if (input === 'k' || input === 'up')
|
|
162
151
|
this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
|
|
152
|
+
else if (input === 'enter')
|
|
153
|
+
return this.activate(this.selectedIndex, context);
|
|
163
154
|
else
|
|
164
155
|
return false;
|
|
165
156
|
context.requestDraw();
|
|
@@ -184,8 +175,7 @@ export class ProjectPickerScreen extends BaseScreen {
|
|
|
184
175
|
return true;
|
|
185
176
|
}
|
|
186
177
|
reset() {
|
|
187
|
-
this.candidates = [];
|
|
188
|
-
this.repo = '';
|
|
189
178
|
this.selectedIndex = 0;
|
|
179
|
+
super.reset();
|
|
190
180
|
}
|
|
191
181
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { VNode } from '@opentui/core';
|
|
2
2
|
import { AsyncScreen } from '../../../Screen/async-screen.js';
|
|
3
|
-
import {
|
|
3
|
+
import type { FullScreenChrome } from '../../../Screen/full-screen.js';
|
|
4
|
+
import type { ScreenContext } from '../../../Screen/screen.js';
|
|
4
5
|
import { type ProjectRow } from '../Data/projects.js';
|
|
5
6
|
/**
|
|
6
7
|
* GAIA-340 D1 — the ready page is a renderable TREE; `loading` and `error` are
|
|
@@ -44,7 +45,8 @@ export declare class ProjectsScreen extends AsyncScreen<ProjectRow[]> {
|
|
|
44
45
|
protected load(context: ScreenContext): Promise<ProjectRow[]>;
|
|
45
46
|
protected renderLoading(): string[];
|
|
46
47
|
protected renderReady(rows: ProjectRow[]): VNode;
|
|
47
|
-
|
|
48
|
+
protected chrome(context: ScreenContext): FullScreenChrome;
|
|
49
|
+
protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
|
|
48
50
|
activate(index: number, context: ScreenContext): boolean;
|
|
49
51
|
select(index: number, context: ScreenContext): boolean;
|
|
50
52
|
handleInput(input: string, context: ScreenContext): boolean;
|