@gaia-ai/addon-gaia-ui 0.9.2 → 0.10.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 +65 -6
- package/dist/src/Component/top-tabs/index.js +29 -6
- package/dist/src/Entity/collection.js +1 -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 +31 -6
- package/dist/src/Entity/statistics/Data/statistics.js +39 -6
- package/dist/src/Entity/statistics/Screen/dashboard.d.ts +10 -2
- package/dist/src/Entity/statistics/Screen/dashboard.js +111 -34
- 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/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 +14 -0
- package/dist/src/Entity/ticket/Data/detail.js +15 -1
- 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 -15
- 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/user/Data/users.d.ts +49 -0
- package/dist/src/Entity/user/Data/users.js +46 -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 -434
- package/dist/src/Routing/context.d.ts +5 -0
- package/dist/src/Routing/context.js +6 -0
- package/dist/src/Routing/registry.js +4 -2
- package/dist/src/Routing/router.js +5 -1
- package/dist/src/Routing/routes.js +8 -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 +6 -0
- package/dist/src/launcher.js +2 -0
- 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 +32 -0
- package/dist/src/ui.d.ts +2 -0
- package/dist/src/ui.js +1 -0
- package/package.json +4 -3
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { cellsRow, textNode } from '../../lib/vnode.js';
|
|
1
|
+
import { cellsRow, textNode, } from '../../lib/vnode.js';
|
|
2
|
+
import { plainLine } from '../markdown/index.js';
|
|
3
|
+
/** Whether a line is columns rather than prose — `cells` is the discriminant. */
|
|
4
|
+
const isCells = (line) => typeof line !== 'string' && 'cells' in line;
|
|
2
5
|
/**
|
|
3
6
|
* A line as the text a surface with no layout prints.
|
|
4
7
|
*
|
|
@@ -6,29 +9,58 @@ import { cellsRow, textNode } from '../../lib/vnode.js';
|
|
|
6
9
|
* puts between two cells: the pipe and the tree then say the same thing about
|
|
7
10
|
* what separates two columns, and only the alignment inside a column is the
|
|
8
11
|
* renderer's own.
|
|
12
|
+
*
|
|
13
|
+
* GAIA-372 D4 — for a {@link StyledLine} this returns the RENDERED text, which is the
|
|
14
|
+
* whole of how parity is achieved: `SectionRow.text` carries it and the pipe
|
|
15
|
+
* prints exactly that, so `gaia ui <id> | cat` and the interactive tree are the
|
|
16
|
+
* same rendering by construction. The tree merely adds the attributes a pipe
|
|
17
|
+
* cannot carry. There is no raw-only pipe path left to disagree (AC-3).
|
|
9
18
|
*/
|
|
10
|
-
export const lineText = (line) =>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
.
|
|
15
|
-
|
|
19
|
+
export const lineText = (line) => {
|
|
20
|
+
if (typeof line === 'string')
|
|
21
|
+
return line;
|
|
22
|
+
if (isCells(line)) {
|
|
23
|
+
return line.cells
|
|
24
|
+
.map((cell) => cell.text)
|
|
25
|
+
.join(' ')
|
|
26
|
+
.replace(/\s+$/, '');
|
|
27
|
+
}
|
|
28
|
+
return line.text;
|
|
29
|
+
};
|
|
16
30
|
/**
|
|
17
|
-
*
|
|
18
|
-
* the markers stripped — the same rule a section heading follows. Bullets and
|
|
19
|
-
* code fences stay raw by design (explicit v1 decision).
|
|
31
|
+
* Any body line as a RENDERED line — GAIA-372 D3/D8.
|
|
20
32
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* make the same body look different depending on which renderer reached it.
|
|
33
|
+
* A plain or columned row is LIFTED (one unparsed span) rather than refused,
|
|
34
|
+
* because a consumer that needs rendered lines — the collapsible Description
|
|
35
|
+
* entry, whose extract reads `line.heading` — is handed a `Description` section
|
|
36
|
+
* whose empty state is the plain string `No description.` Lifting never parses,
|
|
37
|
+
* so nothing here can re-introduce a Markdown rule on the plain path.
|
|
27
38
|
*/
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
export const styledOf = (line) => typeof line === 'string' || isCells(line) ? plainLine(lineText(line)) : line;
|
|
40
|
+
/**
|
|
41
|
+
* One body line as the row it is — the ONE place D3's three variants are read
|
|
42
|
+
* apart, so a flat section and a collapsible entry cannot answer differently for
|
|
43
|
+
* the same line.
|
|
44
|
+
*/
|
|
45
|
+
const bodyRow = (key, line) => {
|
|
46
|
+
// Columns are never read as Markdown: a run number opens with `#`, and the ATX
|
|
47
|
+
// rule would both bold the row and eat the number.
|
|
48
|
+
if (isCells(line)) {
|
|
49
|
+
return { key, text: lineText(line), bold: false, cells: line.cells };
|
|
50
|
+
}
|
|
51
|
+
// A plain string is PLAIN. Nothing parses it (D3) — which is what puts
|
|
52
|
+
// `Assignment`, `Branch`, `Links` and `Labels` structurally out of reach.
|
|
53
|
+
if (typeof line === 'string')
|
|
54
|
+
return { key, text: line, bold: false };
|
|
55
|
+
return {
|
|
56
|
+
key,
|
|
57
|
+
text: line.text,
|
|
58
|
+
// An ATX heading inside a body is the ROW's weight, exactly as it was; what
|
|
59
|
+
// decides it is now the rendered line's own flag rather than a re-match.
|
|
60
|
+
bold: line.heading,
|
|
61
|
+
runs: line.runs,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
32
64
|
/**
|
|
33
65
|
* A heading as an id fragment: `PER USER` → `per-user`.
|
|
34
66
|
*
|
|
@@ -41,6 +73,57 @@ export const sectionKey = (heading) => heading
|
|
|
41
73
|
.toLowerCase()
|
|
42
74
|
.replace(/[^a-z0-9]+/g, '-')
|
|
43
75
|
.replace(/^-|-$/g, '');
|
|
76
|
+
/**
|
|
77
|
+
* The same, for a heading that is DATA rather than one of the built-in set —
|
|
78
|
+
* GAIA-373 AC-10.
|
|
79
|
+
*
|
|
80
|
+
* {@link sectionKey} is LOSSY: it lowercases and collapses every run of
|
|
81
|
+
* non-alphanumeric characters onto one `-`. That is injective over the closed
|
|
82
|
+
* built-in headings (`Per user`, `Latest summary`), and it stops being injective
|
|
83
|
+
* the moment a contributed panel heads its sections with whatever it is
|
|
84
|
+
* listing. The shipped artifacts viewer heads them with paths under the ticket's
|
|
85
|
+
* tree, so `/`, `.` and `-` all land on the same character and two ordinary
|
|
86
|
+
* captures collapse onto one key:
|
|
87
|
+
*
|
|
88
|
+
* playwright/logs/walk.txt -> playwright-logs-walk-txt
|
|
89
|
+
* playwright/logs-walk.txt -> playwright-logs-walk-txt
|
|
90
|
+
*
|
|
91
|
+
* A key is an IDENTITY — `Kernel/mount.ts` refuses a repeated id, and it refuses
|
|
92
|
+
* it while MOUNTING, past the host's guard around `panel.sections()` and on a
|
|
93
|
+
* draw path with no `catch` — so a lossy key is not a cosmetic collision, it is
|
|
94
|
+
* the cockpit going down on a message naming neither the addon nor the file.
|
|
95
|
+
*
|
|
96
|
+
* SO THE SEPARATORS ARE ENCODED RATHER THAN COLLAPSED. Every UTF-8 byte outside
|
|
97
|
+
* `[a-z0-9]` becomes `_<hex>`, two digits, lower case. The escape has a fixed
|
|
98
|
+
* length and `_` is itself escaped (`_5f`), so the mapping is reversible and
|
|
99
|
+
* therefore injective — including over case (`README.txt` and `readme.txt` are
|
|
100
|
+
* two files on this filesystem) and over a heading that already looks like an
|
|
101
|
+
* escape.
|
|
102
|
+
*
|
|
103
|
+
* WHY THE OUTPUT CARRIES NO `-`, which is a second property and not a side
|
|
104
|
+
* effect. A key never becomes an id on its own: it is always suffixed
|
|
105
|
+
* (`-heading`, `-gap`, `-empty`, `-header`, `-<index>`). Injectivity of the key
|
|
106
|
+
* alone would not settle the id, because the suffix boundary would be
|
|
107
|
+
* ambiguous — with `-` allowed inside a key, section `a` at body index 12 and
|
|
108
|
+
* section `a-1` at body index 2 would both be `a-12`. With no `-` inside a key
|
|
109
|
+
* the first `-` after the screen's own prefix always ends the key, so a key and
|
|
110
|
+
* its suffix are both recovered from an id and two ids can only be equal when
|
|
111
|
+
* both halves are. Within one section the suffixes are distinct by construction:
|
|
112
|
+
* the indices are digits and the four literals are not.
|
|
113
|
+
*
|
|
114
|
+
* NOT THE BUILT-IN PATH. `sectionRows` and the ticket detail's tabs keep
|
|
115
|
+
* {@link sectionKey} as their default, so no built-in node id moves (AC-2) — a
|
|
116
|
+
* `_` in a key is exactly the mark of a heading that came from a contribution.
|
|
117
|
+
*
|
|
118
|
+
* WHAT THIS STILL DOES NOT FIX, said plainly: two panel sections carrying the
|
|
119
|
+
* SAME heading. No injective map over headings can separate two identical
|
|
120
|
+
* headings, so that case remains the loud refusal it is today. It is out of
|
|
121
|
+
* scope for GAIA-373 and belongs to whatever gives a panel section an identity
|
|
122
|
+
* distinct from its title.
|
|
123
|
+
*/
|
|
124
|
+
export const panelSectionKey = (heading) => Array.from(new TextEncoder().encode(heading), (byte) => (byte >= 0x30 && byte <= 0x39) || (byte >= 0x61 && byte <= 0x7a)
|
|
125
|
+
? String.fromCharCode(byte)
|
|
126
|
+
: `_${byte.toString(16).padStart(2, '0')}`).join('');
|
|
44
127
|
/**
|
|
45
128
|
* Flatten `{heading, lines}` sections into KEYED rows: the heading, its body,
|
|
46
129
|
* and the blank line that closes the section.
|
|
@@ -51,21 +134,18 @@ export const sectionKey = (heading) => heading
|
|
|
51
134
|
* property that lets the Dashboard hand over renderables while its pipe keeps
|
|
52
135
|
* emitting the bytes it always did (spec F5).
|
|
53
136
|
*/
|
|
54
|
-
export function sectionRows(sections
|
|
137
|
+
export function sectionRows(sections,
|
|
138
|
+
// GAIA-373 — HOW a heading becomes a key, as an argument. The built-in
|
|
139
|
+
// derivation stays the default so every existing caller keys exactly as it
|
|
140
|
+
// did (AC-2); a caller whose headings are data passes
|
|
141
|
+
// {@link panelSectionKey}, which is injective over them.
|
|
142
|
+
keyOf = sectionKey) {
|
|
55
143
|
const out = [];
|
|
56
144
|
for (const section of sections) {
|
|
57
|
-
const id =
|
|
145
|
+
const id = keyOf(section.heading);
|
|
58
146
|
out.push({ key: `${id}-heading`, text: section.heading, bold: true });
|
|
59
147
|
section.lines.forEach((line, index) => {
|
|
60
|
-
|
|
61
|
-
// Columns are never read as markdown: a run number opens with `#`, and the
|
|
62
|
-
// ATX rule would both bold the row and eat the number.
|
|
63
|
-
if (typeof line !== 'string') {
|
|
64
|
-
out.push({ key, text: lineText(line), bold: false, cells: line.cells });
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const { text, bold } = markdownRow(line);
|
|
68
|
-
out.push({ key, text, bold });
|
|
148
|
+
out.push(bodyRow(`${id}-${index}`, line));
|
|
69
149
|
});
|
|
70
150
|
out.push({ key: `${id}-gap`, text: '', bold: false });
|
|
71
151
|
}
|
|
@@ -85,10 +165,17 @@ export const sectionRowNode = (prefix, row, selected = false) => row.cells === u
|
|
|
85
165
|
content: row.text,
|
|
86
166
|
bold: row.bold,
|
|
87
167
|
selected,
|
|
168
|
+
// GAIA-372 D5 — spread rather than passed as `undefined`, for
|
|
169
|
+
// `exactOptionalPropertyTypes`: a row with no runs declares none, and
|
|
170
|
+
// `linked()` then returns the plain string it always did.
|
|
171
|
+
...(row.runs === undefined ? {} : { runs: row.runs }),
|
|
88
172
|
})
|
|
89
173
|
: cellsRow(`${prefix}-${row.key}`, row.cells, selected);
|
|
90
174
|
// The same rows as plain text plus the INDICES that are bold (section headings +
|
|
91
|
-
//
|
|
175
|
+
// a rendered body's own ATX headings) — the shape the row surface needs. The
|
|
176
|
+
// RUNS are dropped on this path by definition: a pipe carries no styling, and
|
|
177
|
+
// `row.text` is already the rendered text, so what it prints is what the tree
|
|
178
|
+
// draws minus the attributes (GAIA-372 D4). Headings carry NO
|
|
92
179
|
// ANSI here: the draw step applies bold after fit(), so sanitizeDisplay never
|
|
93
180
|
// sees (and never strips) the ESC.
|
|
94
181
|
export function flattenSections(sections) {
|
|
@@ -138,8 +225,7 @@ export function expandableRows(entries, { expanded = new Set(), emptyMessage, em
|
|
|
138
225
|
});
|
|
139
226
|
const body = expanded.has(i) ? entry.expanded : entry.collapsed;
|
|
140
227
|
body.forEach((line, index) => {
|
|
141
|
-
|
|
142
|
-
rows.push({ key: `${entry.key}-${index}`, text, bold });
|
|
228
|
+
rows.push(bodyRow(`${entry.key}-${index}`, line));
|
|
143
229
|
});
|
|
144
230
|
});
|
|
145
231
|
return { rows, headerRows, selectable: entries.length };
|
|
@@ -13,6 +13,9 @@ export declare const ROOT_TABS: readonly [{
|
|
|
13
13
|
}, {
|
|
14
14
|
readonly id: "conductors";
|
|
15
15
|
readonly label: "Conductors";
|
|
16
|
+
}, {
|
|
17
|
+
readonly id: "users";
|
|
18
|
+
readonly label: "Users";
|
|
16
19
|
}];
|
|
17
20
|
/** The project level: the tabs of an opened project. */
|
|
18
21
|
export declare const PROJECT_TABS: readonly [{
|
|
@@ -62,6 +65,22 @@ export declare const TICKET_TABS: readonly [{
|
|
|
62
65
|
readonly id: "runs";
|
|
63
66
|
readonly label: "Runs";
|
|
64
67
|
}];
|
|
68
|
+
/**
|
|
69
|
+
* The user level: Overview · Tickets · Runs for one person (GAIA-377 D1).
|
|
70
|
+
*
|
|
71
|
+
* Destinations, not modes — each tab is its own screen in the slice that owns
|
|
72
|
+
* its query (D2), so Tab runs a real `Router.navigate` and preserves `userId`.
|
|
73
|
+
*/
|
|
74
|
+
export declare const USER_TABS: readonly [{
|
|
75
|
+
readonly id: "overview";
|
|
76
|
+
readonly label: "Overview";
|
|
77
|
+
}, {
|
|
78
|
+
readonly id: "tickets";
|
|
79
|
+
readonly label: "Tickets";
|
|
80
|
+
}, {
|
|
81
|
+
readonly id: "runs";
|
|
82
|
+
readonly label: "Runs";
|
|
83
|
+
}];
|
|
65
84
|
/** The tab bars, keyed by level — the one place a level is named. */
|
|
66
85
|
export declare const TAB_LEVELS: {
|
|
67
86
|
readonly root: readonly [{
|
|
@@ -73,6 +92,9 @@ export declare const TAB_LEVELS: {
|
|
|
73
92
|
}, {
|
|
74
93
|
readonly id: "conductors";
|
|
75
94
|
readonly label: "Conductors";
|
|
95
|
+
}, {
|
|
96
|
+
readonly id: "users";
|
|
97
|
+
readonly label: "Users";
|
|
76
98
|
}];
|
|
77
99
|
readonly project: readonly [{
|
|
78
100
|
readonly id: "dashboard";
|
|
@@ -100,6 +122,16 @@ export declare const TAB_LEVELS: {
|
|
|
100
122
|
readonly id: "runs";
|
|
101
123
|
readonly label: "Runs";
|
|
102
124
|
}];
|
|
125
|
+
readonly user: readonly [{
|
|
126
|
+
readonly id: "overview";
|
|
127
|
+
readonly label: "Overview";
|
|
128
|
+
}, {
|
|
129
|
+
readonly id: "tickets";
|
|
130
|
+
readonly label: "Tickets";
|
|
131
|
+
}, {
|
|
132
|
+
readonly id: "runs";
|
|
133
|
+
readonly label: "Runs";
|
|
134
|
+
}];
|
|
103
135
|
};
|
|
104
136
|
/** The name of a level that carries a top-level tab bar. */
|
|
105
137
|
export type TabLevel = keyof typeof TAB_LEVELS;
|
|
@@ -117,13 +149,34 @@ export type TopTabId = {
|
|
|
117
149
|
* Where a screen sits in the navigation: which bar it draws, and which tab of
|
|
118
150
|
* that bar is its own. A discriminated union over the levels, so a location can
|
|
119
151
|
* never pair one level's name with another level's tab id.
|
|
152
|
+
*
|
|
153
|
+
* GAIA-373 D5/D6 — the `ticket` arm is the one that is NOT a literal union, and
|
|
154
|
+
* it is also the one that carries an optional bar of its own. Both halves have
|
|
155
|
+
* the same cause: the ticket level is not a DESTINATION level.
|
|
156
|
+
* `router.navigateTab` is never reached for it, because `selectTopTab` claims
|
|
157
|
+
* every ticket tab (GAIA-346 D4), so the literal union that protects root/project
|
|
158
|
+
* NAVIGATION has no target here — while a contributed section mode
|
|
159
|
+
* (`gaia/artifacts`) is data from the contribution store and cannot be a literal
|
|
160
|
+
* in this file at all. Root and project keep theirs intact, so
|
|
161
|
+
* `topTabBar('root', 'runs')` is still a TS2345.
|
|
162
|
+
*
|
|
163
|
+
* `tabs` is read by BOTH bar builders below and by the kernel's Tab cycle, so
|
|
164
|
+
* the bar drawn, the bar clicked and the bar cycled are three readings of one
|
|
165
|
+
* field rather than three answers.
|
|
120
166
|
*/
|
|
121
167
|
export type TabLocation = {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
168
|
+
level: 'root';
|
|
169
|
+
tab: TabIdOf<'root'>;
|
|
170
|
+
} | {
|
|
171
|
+
level: 'project';
|
|
172
|
+
tab: TabIdOf<'project'>;
|
|
173
|
+
} | {
|
|
174
|
+
level: 'ticket';
|
|
175
|
+
tab: string;
|
|
176
|
+
tabs?: readonly TopTab[];
|
|
177
|
+
};
|
|
178
|
+
/** The entries a location draws: its own, else its level's registry. */
|
|
179
|
+
export declare const tabsOfLocation: (location: TabLocation) => readonly TopTab[];
|
|
127
180
|
/**
|
|
128
181
|
* The bar as the PIECES it is drawn from: one part per tab, carrying that tab's
|
|
129
182
|
* id, with the joins between them carrying none.
|
|
@@ -191,5 +244,11 @@ export declare function topTabParts<L extends TabLevel>(level: L, activeId: TabI
|
|
|
191
244
|
* companion of {@link tabBarFor}, and the pairing it needs was already checked
|
|
192
245
|
* when the location was built. Keeping the two in step is what makes "the bar you
|
|
193
246
|
* see is the bar you click" true by construction: both read `location`.
|
|
247
|
+
*
|
|
248
|
+
* GAIA-373 — `TabBarPart<string>` rather than the `TopTabId` default, and that
|
|
249
|
+
* is an honest widening rather than a lost check: a location may carry a bar of
|
|
250
|
+
* its own now, whose entries are CONTRIBUTED (`gaia/artifacts`) and by
|
|
251
|
+
* construction outside every union derived from `TAB_LEVELS`. The level-typed
|
|
252
|
+
* {@link topTabParts} keeps the narrow ids for callers that name a level.
|
|
194
253
|
*/
|
|
195
|
-
export declare function tabPartsFor(location: TabLocation): TabBarPart[];
|
|
254
|
+
export declare function tabPartsFor(location: TabLocation): TabBarPart<string>[];
|
|
@@ -29,10 +29,9 @@
|
|
|
29
29
|
// level — the bar you Tab through or click IS the current level's bar — so it
|
|
30
30
|
// pairs the target with `here.level` directly and `locateTab` is deleted.
|
|
31
31
|
//
|
|
32
|
-
// Only tabs that are actually navigable are listed.
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
// is the very defect GAIA-239 removes.
|
|
32
|
+
// Only tabs that are actually navigable are listed. GAIA-377 added `Users` with
|
|
33
|
+
// its screens; a tab drawn before its screen exists would advertise navigation
|
|
34
|
+
// the operator cannot take, which is the very defect GAIA-239 removes.
|
|
36
35
|
//
|
|
37
36
|
// GAIA-232 added `conductors`, and put it on the ROOT level although the note
|
|
38
37
|
// above once reserved it for the project level. A conductor is an operator
|
|
@@ -53,6 +52,8 @@ export const ROOT_TABS = [
|
|
|
53
52
|
{ id: 'dashboard', label: 'Dashboard' },
|
|
54
53
|
{ id: 'projects', label: 'Projects' },
|
|
55
54
|
{ id: 'conductors', label: 'Conductors' },
|
|
55
|
+
// GAIA-377 D1 — arrives with its screens; see USER_TABS.
|
|
56
|
+
{ id: 'users', label: 'Users' },
|
|
56
57
|
];
|
|
57
58
|
/** The project level: the tabs of an opened project. */
|
|
58
59
|
export const PROJECT_TABS = [
|
|
@@ -88,12 +89,28 @@ export const TICKET_TABS = [
|
|
|
88
89
|
{ id: 'related', label: 'Related' },
|
|
89
90
|
{ id: 'runs', label: 'Runs' },
|
|
90
91
|
];
|
|
92
|
+
/**
|
|
93
|
+
* The user level: Overview · Tickets · Runs for one person (GAIA-377 D1).
|
|
94
|
+
*
|
|
95
|
+
* Destinations, not modes — each tab is its own screen in the slice that owns
|
|
96
|
+
* its query (D2), so Tab runs a real `Router.navigate` and preserves `userId`.
|
|
97
|
+
*/
|
|
98
|
+
export const USER_TABS = [
|
|
99
|
+
{ id: 'overview', label: 'Overview' },
|
|
100
|
+
{ id: 'tickets', label: 'Tickets' },
|
|
101
|
+
{ id: 'runs', label: 'Runs' },
|
|
102
|
+
];
|
|
91
103
|
/** The tab bars, keyed by level — the one place a level is named. */
|
|
92
104
|
export const TAB_LEVELS = {
|
|
93
105
|
root: ROOT_TABS,
|
|
94
106
|
project: PROJECT_TABS,
|
|
95
107
|
ticket: TICKET_TABS,
|
|
108
|
+
user: USER_TABS,
|
|
96
109
|
};
|
|
110
|
+
/** The entries a location draws: its own, else its level's registry. */
|
|
111
|
+
export const tabsOfLocation = (location) => 'tabs' in location && location.tabs
|
|
112
|
+
? location.tabs
|
|
113
|
+
: TAB_LEVELS[location.level];
|
|
97
114
|
/** What joins two tabs in the rendered bar — part of neither tab's span. */
|
|
98
115
|
const SEPARATOR = ' ';
|
|
99
116
|
/** One tab's rendered segment: bracketed when active, space-padded otherwise. */
|
|
@@ -133,7 +150,7 @@ export function topTabBar(level, activeId) {
|
|
|
133
150
|
* not ask the compiler to correlate two independently narrowed properties.
|
|
134
151
|
*/
|
|
135
152
|
export function tabBarFor(location) {
|
|
136
|
-
return renderBar(
|
|
153
|
+
return renderBar(tabsOfLocation(location), location.tab);
|
|
137
154
|
}
|
|
138
155
|
// GAIA-256 spec D2 — `locateTab` USED TO LIVE HERE and is deliberately gone.
|
|
139
156
|
//
|
|
@@ -168,7 +185,13 @@ export function topTabParts(level, activeId) {
|
|
|
168
185
|
* companion of {@link tabBarFor}, and the pairing it needs was already checked
|
|
169
186
|
* when the location was built. Keeping the two in step is what makes "the bar you
|
|
170
187
|
* see is the bar you click" true by construction: both read `location`.
|
|
188
|
+
*
|
|
189
|
+
* GAIA-373 — `TabBarPart<string>` rather than the `TopTabId` default, and that
|
|
190
|
+
* is an honest widening rather than a lost check: a location may carry a bar of
|
|
191
|
+
* its own now, whose entries are CONTRIBUTED (`gaia/artifacts`) and by
|
|
192
|
+
* construction outside every union derived from `TAB_LEVELS`. The level-typed
|
|
193
|
+
* {@link topTabParts} keeps the narrow ids for callers that name a level.
|
|
171
194
|
*/
|
|
172
195
|
export function tabPartsFor(location) {
|
|
173
|
-
return renderParts(
|
|
196
|
+
return renderParts(tabsOfLocation(location), location.tab);
|
|
174
197
|
}
|
|
@@ -50,7 +50,7 @@ function readTotal(doc) {
|
|
|
50
50
|
* back is `pad`'s reason for existing: `cellFill` fills to a width the COLUMN
|
|
51
51
|
* DECLARES, never to one measured off the page.
|
|
52
52
|
* Reaching `lib/width.ts` for the measurement is a DELIBERATE decision on top of
|
|
53
|
-
* A2 already being reported as missed (`docs/evidence/GAIA-340/teardown-a2.txt
|
|
53
|
+
* A2 already being reported as missed (`docs/evidence/GAIA-340/teardown-a2.txt`, in git history since GAIA-371)
|
|
54
54
|
* — the module survives this ticket, so the alternative is not "no measurement"
|
|
55
55
|
* but "an illegible cut", and R5's rule against re-implementing it under another
|
|
56
56
|
* name is honoured by calling it rather than copying it.
|
|
@@ -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 {};
|