@gaia-ai/addon-gaia-ui 0.7.0 → 0.9.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/Agent/launch.d.ts +4 -4
- package/dist/src/Agent/launch.js +14 -7
- package/dist/src/Component/section-list/index.d.ts +117 -8
- package/dist/src/Component/section-list/index.js +118 -46
- package/dist/src/Component/stats-panel/index.d.ts +17 -5
- package/dist/src/Component/stats-panel/index.js +50 -52
- package/dist/src/Component/top-tabs/index.d.ts +95 -23
- package/dist/src/Component/top-tabs/index.js +87 -41
- package/dist/src/Entity/collection.d.ts +279 -0
- package/dist/src/Entity/collection.js +377 -0
- package/dist/src/Entity/conductor/Component/conductor-list/index.d.ts +25 -0
- package/dist/src/Entity/conductor/Component/conductor-list/index.js +60 -0
- package/dist/src/Entity/conductor/Data/conductors.d.ts +216 -0
- package/dist/src/Entity/conductor/Data/conductors.js +280 -0
- package/dist/src/Entity/conductor/Data/liveness.d.ts +2 -1
- package/dist/src/Entity/conductor/Data/liveness.js +12 -16
- package/dist/src/Entity/conductor/Data/log-tail.d.ts +25 -0
- package/dist/src/Entity/conductor/Data/log-tail.js +133 -0
- package/dist/src/Entity/conductor/Data/own-conductor.d.ts +30 -0
- package/dist/src/Entity/conductor/Data/own-conductor.js +73 -0
- package/dist/src/Entity/conductor/Data/process.d.ts +78 -0
- package/dist/src/Entity/conductor/Data/process.js +101 -0
- package/dist/src/Entity/conductor/Screen/conductors.d.ts +67 -0
- package/dist/src/Entity/conductor/Screen/conductors.js +233 -0
- package/dist/src/Entity/cycle.d.ts +18 -0
- package/dist/src/Entity/cycle.js +30 -0
- package/dist/src/Entity/project/Data/project.d.ts +8 -0
- package/dist/src/Entity/project/Data/project.js +12 -0
- package/dist/src/Entity/project/Data/projects.d.ts +121 -0
- package/dist/src/Entity/project/Data/projects.js +115 -0
- package/dist/src/Entity/project/Data/with-tickets.d.ts +5 -0
- package/dist/src/Entity/project/Data/with-tickets.js +15 -13
- package/dist/src/Entity/project/Screen/project-picker.d.ts +80 -0
- package/dist/src/Entity/project/Screen/project-picker.js +191 -0
- package/dist/src/Entity/project/Screen/projects-screen.d.ts +52 -0
- package/dist/src/Entity/project/Screen/projects-screen.js +122 -0
- package/dist/src/Entity/run/Component/run-detail/index.d.ts +26 -10
- package/dist/src/Entity/run/Component/run-detail/index.js +147 -39
- package/dist/src/Entity/run/Component/run-table/index.d.ts +5 -7
- package/dist/src/Entity/run/Component/run-table/index.js +9 -18
- package/dist/src/Entity/run/Data/query.d.ts +22 -0
- package/dist/src/Entity/run/Data/query.js +41 -0
- package/dist/src/Entity/run/Data/runs.d.ts +192 -4
- package/dist/src/Entity/run/Data/runs.js +188 -27
- package/dist/src/Entity/run/Screen/run-detail.d.ts +53 -0
- package/dist/src/Entity/run/Screen/run-detail.js +70 -0
- package/dist/src/Entity/run/Screen/runs.d.ts +86 -0
- package/dist/src/Entity/run/Screen/runs.js +169 -0
- package/dist/src/Entity/statistics/Screen/dashboard.d.ts +78 -0
- package/dist/src/Entity/statistics/Screen/dashboard.js +228 -0
- package/dist/src/Entity/ticket/Component/comment-item/index.d.ts +12 -9
- package/dist/src/Entity/ticket/Component/comment-item/index.js +18 -13
- package/dist/src/Entity/ticket/Component/create-form/index.d.ts +16 -3
- package/dist/src/Entity/ticket/Component/create-form/index.js +28 -14
- package/dist/src/Entity/ticket/Component/edit-form/index.d.ts +11 -6
- package/dist/src/Entity/ticket/Component/edit-form/index.js +77 -29
- package/dist/src/Entity/ticket/Component/sections/index.d.ts +8 -0
- package/dist/src/Entity/ticket/Component/sections/index.js +42 -0
- package/dist/src/Entity/ticket/Component/tab-bar/index.js +6 -0
- package/dist/src/Entity/ticket/Component/ticket-detail/index.d.ts +65 -8
- package/dist/src/Entity/ticket/Component/ticket-detail/index.js +171 -84
- package/dist/src/Entity/ticket/Component/ticket-table/index.d.ts +4 -2
- package/dist/src/Entity/ticket/Component/ticket-table/index.js +8 -12
- package/dist/src/Entity/ticket/Component/ticket-teaser/index.d.ts +47 -3
- package/dist/src/Entity/ticket/Component/ticket-teaser/index.js +76 -22
- package/dist/src/Entity/ticket/Data/parents.d.ts +13 -1
- package/dist/src/Entity/ticket/Data/parents.js +64 -12
- package/dist/src/Entity/ticket/Data/query.d.ts +44 -7
- package/dist/src/Entity/ticket/Data/query.js +60 -26
- package/dist/src/Entity/ticket/Data/tickets.d.ts +170 -4
- package/dist/src/Entity/ticket/Data/tickets.js +200 -12
- package/dist/src/Entity/ticket/Data/url.d.ts +16 -0
- package/dist/src/Entity/ticket/Data/url.js +20 -0
- package/dist/src/Entity/ticket/Data/workflow.d.ts +1 -5
- package/dist/src/Entity/ticket/Data/workflow.js +5 -13
- package/dist/src/Entity/ticket/Data/write.d.ts +32 -0
- package/dist/src/Entity/ticket/Data/write.js +80 -0
- package/dist/src/Entity/ticket/Form/create-data.d.ts +1 -11
- package/dist/src/Entity/ticket/Form/create-data.js +7 -36
- package/dist/src/Entity/ticket/Form/create.d.ts +45 -5
- package/dist/src/Entity/ticket/Form/create.js +45 -8
- package/dist/src/Entity/ticket/Form/edit-data.d.ts +1 -1
- package/dist/src/Entity/ticket/Form/edit-data.js +4 -0
- package/dist/src/Entity/ticket/Form/edit.d.ts +79 -12
- package/dist/src/Entity/ticket/Form/edit.js +165 -18
- package/dist/src/Entity/ticket/Form/keys.d.ts +7 -0
- package/dist/src/Entity/ticket/Form/keys.js +7 -0
- package/dist/src/Entity/ticket/Form/parent-filter.d.ts +36 -0
- package/dist/src/Entity/ticket/Form/parent-filter.js +37 -0
- package/dist/src/Entity/ticket/Form/parent-source.d.ts +27 -0
- package/dist/src/Entity/ticket/Form/parent-source.js +56 -0
- package/dist/src/Entity/ticket/Form/who-filter.d.ts +33 -0
- package/dist/src/Entity/ticket/Form/who-filter.js +33 -0
- package/dist/src/Entity/ticket/Form/who-source.d.ts +18 -0
- package/dist/src/Entity/ticket/Form/who-source.js +38 -0
- package/dist/src/Entity/ticket/Screen/create-ticket-form.d.ts +81 -0
- package/dist/src/Entity/ticket/Screen/create-ticket-form.js +356 -0
- package/dist/src/Entity/ticket/Screen/edit-assignment-form.d.ts +92 -0
- package/dist/src/Entity/ticket/Screen/edit-assignment-form.js +220 -0
- package/dist/src/Entity/ticket/Screen/parent-filter-form.d.ts +44 -0
- package/dist/src/Entity/ticket/Screen/parent-filter-form.js +200 -0
- package/dist/src/Entity/ticket/Screen/ticket-agents.d.ts +136 -0
- package/dist/src/Entity/ticket/Screen/ticket-agents.js +257 -0
- package/dist/src/Entity/ticket/Screen/ticket-detail.d.ts +125 -0
- package/dist/src/Entity/ticket/Screen/ticket-detail.js +415 -0
- package/dist/src/Entity/ticket/Screen/tickets.d.ts +145 -0
- package/dist/src/Entity/ticket/Screen/tickets.js +538 -0
- package/dist/src/Entity/ticket/Screen/who-filter-form.d.ts +41 -0
- package/dist/src/Entity/ticket/Screen/who-filter-form.js +174 -0
- package/dist/src/Entity/user/Data/users.d.ts +17 -0
- package/dist/src/Entity/user/Data/users.js +66 -6
- package/dist/src/Form/field.d.ts +3 -1
- package/dist/src/Form/field.js +1 -0
- package/dist/src/Form/form.d.ts +1 -1
- package/dist/src/Form/form.js +61 -9
- package/dist/src/Form/keys.d.ts +23 -1
- package/dist/src/Form/keys.js +78 -23
- package/dist/src/Kernel/chrome.d.ts +289 -0
- package/dist/src/Kernel/chrome.js +404 -0
- package/dist/src/Kernel/clipboard.d.ts +30 -0
- package/dist/src/Kernel/clipboard.js +51 -0
- package/dist/src/Kernel/key-input.d.ts +50 -0
- package/dist/src/Kernel/key-input.js +158 -0
- package/dist/src/Kernel/mount.d.ts +187 -0
- package/dist/src/Kernel/mount.js +399 -0
- package/dist/src/Kernel/tui-kernel.d.ts +0 -11
- package/dist/src/Kernel/tui-kernel.js +778 -2367
- package/dist/src/Routing/context.d.ts +31 -0
- package/dist/src/Routing/context.js +52 -0
- package/dist/src/Routing/registry.d.ts +19 -0
- package/dist/src/Routing/registry.js +97 -0
- package/dist/src/Routing/route.d.ts +35 -0
- package/dist/src/Routing/route.js +1 -0
- package/dist/src/Routing/router.d.ts +49 -0
- package/dist/src/Routing/router.js +123 -0
- package/dist/src/Routing/routes.d.ts +17 -0
- package/dist/src/Routing/routes.js +57 -0
- package/dist/src/Screen/async-screen.d.ts +87 -0
- package/dist/src/Screen/async-screen.js +206 -0
- package/dist/src/Screen/list-screen.d.ts +193 -0
- package/dist/src/Screen/list-screen.js +261 -0
- package/dist/src/Screen/screen.d.ts +240 -0
- package/dist/src/Screen/screen.js +34 -0
- package/dist/src/Tui/controller.d.ts +13 -0
- package/dist/src/Tui/controller.js +1794 -0
- package/dist/src/Widget/autocomplete.js +75 -3
- package/dist/src/Widget/index.d.ts +3 -2
- package/dist/src/Widget/index.js +2 -1
- package/dist/src/Widget/option-source.d.ts +12 -1
- package/dist/src/Widget/option-source.js +14 -1
- package/dist/src/Widget/render.d.ts +23 -8
- package/dist/src/Widget/render.js +108 -41
- package/dist/src/Widget/text.js +94 -5
- package/dist/src/Widget/textarea.d.ts +45 -0
- package/dist/src/Widget/textarea.js +115 -0
- package/dist/src/Widget/types.d.ts +64 -1
- package/dist/src/launcher.d.ts +9 -2
- package/dist/src/launcher.js +2 -1
- package/dist/src/lib/ansi.js +13 -5
- package/dist/src/lib/clipboard.d.ts +41 -0
- package/dist/src/lib/clipboard.js +65 -0
- package/dist/src/lib/cursor.d.ts +31 -0
- package/dist/src/lib/cursor.js +45 -0
- package/dist/src/lib/format.d.ts +21 -13
- package/dist/src/lib/format.js +76 -28
- package/dist/src/lib/hyperlink.d.ts +58 -0
- package/dist/src/lib/hyperlink.js +104 -0
- package/dist/src/lib/input-trace.d.ts +48 -0
- package/dist/src/lib/input-trace.js +47 -0
- package/dist/src/lib/key-vocabulary.d.ts +19 -0
- package/dist/src/lib/key-vocabulary.js +25 -0
- package/dist/src/lib/table.d.ts +37 -0
- package/dist/src/lib/table.js +20 -0
- package/dist/src/lib/vnode.d.ts +217 -0
- package/dist/src/lib/vnode.js +456 -0
- package/dist/src/lib/width.d.ts +25 -1
- package/dist/src/lib/width.js +93 -12
- package/dist/src/plugin.js +26 -1
- package/dist/src/types.d.ts +67 -2
- package/dist/src/types.js +0 -4
- package/package.json +4 -2
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// The ONE JSON:API collection-document shape and the ONE page read (GAIA-294).
|
|
2
|
+
//
|
|
3
|
+
// Slice-less on purpose, directly under `Entity/` beside `transport.ts`,
|
|
4
|
+
// `count.ts` and `cycle.ts`. That is the only placement every slice's `Data/` may
|
|
5
|
+
// import: `tests/boundary.test.ts` G2 forbids a data module from reaching another
|
|
6
|
+
// slice, and its `sliceOf` returns undefined for a file sitting here, so these
|
|
7
|
+
// edges are not cross-slice ones.
|
|
8
|
+
//
|
|
9
|
+
// Before this file the document was declared FIVE times — in ticket/Data/tickets,
|
|
10
|
+
// run/Data/runs, ticket/Data/parents, project/Data/projects and
|
|
11
|
+
// conductor/Data/own-conductor — and only the first two carried `links.next`. Same
|
|
12
|
+
// name, different shapes: the drift GAIA-292's "mirroring Task 3 exactly so the
|
|
13
|
+
// two lists cannot drift" was meant to prevent had already happened.
|
|
14
|
+
import { columnNode, rowNodeId, seamlessRowNode, textNode, } from '../lib/vnode.js';
|
|
15
|
+
import { displayWidth, truncateToWidth } from '../lib/width.js';
|
|
16
|
+
/**
|
|
17
|
+
* The collection total a document reports, or null when it reports none.
|
|
18
|
+
*
|
|
19
|
+
* Defensive by contract rather than by suspicion: this value is rendered to the
|
|
20
|
+
* operator as a total, so anything that is not a usable count must become the
|
|
21
|
+
* explicit unknown instead of a guess (AC 8.5). A count that is absent, not a
|
|
22
|
+
* number, not finite, or negative is no count.
|
|
23
|
+
*/
|
|
24
|
+
function readTotal(doc) {
|
|
25
|
+
const count = doc?.meta?.count;
|
|
26
|
+
if (typeof count !== 'number')
|
|
27
|
+
return null;
|
|
28
|
+
if (!Number.isFinite(count) || count < 0)
|
|
29
|
+
return null;
|
|
30
|
+
return count;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* One cell's text: the value, cut with an ELLIPSIS when it does not fit.
|
|
34
|
+
*
|
|
35
|
+
* PLACEMENT of the cell is Yoga's — a column's `width` is its budget, and
|
|
36
|
+
* `truncate` (`lib/vnode.ts`) is the renderer's backstop. What Yoga cannot say is
|
|
37
|
+
* that something was REMOVED: `@opentui/core@0.5.1` cuts at the last word
|
|
38
|
+
* boundary inside the budget and appends nothing, so `cancelled` reached the
|
|
39
|
+
* operator as `cancelle` in the ticket list's 8-column STATE column and
|
|
40
|
+
* `qualification` as `qualific` — a cut value that reads as a typo. Measured on a
|
|
41
|
+
* real pty at 120x40; the ellipsis is what makes the difference legible.
|
|
42
|
+
*
|
|
43
|
+
* `lib/format.ts`'s deleted `pad()` cut at `width - 1` and appended `…`, and this
|
|
44
|
+
* is that one behaviour and nothing else. The padding it also did is NOT Yoga's —
|
|
45
|
+
* an earlier revision of this comment said so, and GAIA-345 made it false in the
|
|
46
|
+
* same commit that wrote it: a Yoga budget places a cell but writes no glyph into
|
|
47
|
+
* the columns its value does not reach, which is why the selection highlight came
|
|
48
|
+
* out in fragments. The pad is {@link cellFill}'s again, deliberately, and the
|
|
49
|
+
* argument for it is written there and in {@link tableRow}. What did NOT come
|
|
50
|
+
* back is `pad`'s reason for existing: `cellFill` fills to a width the COLUMN
|
|
51
|
+
* DECLARES, never to one measured off the page.
|
|
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`)
|
|
54
|
+
* — the module survives this ticket, so the alternative is not "no measurement"
|
|
55
|
+
* but "an illegible cut", and R5's rule against re-implementing it under another
|
|
56
|
+
* name is honoured by calling it rather than copying it.
|
|
57
|
+
*
|
|
58
|
+
* `width <= 0` is the TRAILING column — "no budget", not "zero budget": it takes
|
|
59
|
+
* whatever the fixed columns left, how much that is is Yoga's answer, and `pad`
|
|
60
|
+
* rendered it raw for the same reason.
|
|
61
|
+
*/
|
|
62
|
+
const cellText = (raw, width) => {
|
|
63
|
+
const text = raw === null || raw === undefined ? '' : String(raw);
|
|
64
|
+
if (width <= 0 || displayWidth(text) <= width)
|
|
65
|
+
return text;
|
|
66
|
+
return `${truncateToWidth(text, width - 1)}…`;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* The fill for a cell whose resolved width nothing declares — a GROWING column.
|
|
70
|
+
*
|
|
71
|
+
* A grower has no declared width by construction: it takes whatever the row's
|
|
72
|
+
* budgeted columns left, and how much that is is Yoga's answer, arrived at long
|
|
73
|
+
* after this tree is built. So its content is padded PAST any width it could
|
|
74
|
+
* resolve to and the renderer cuts the surplus — measured: a `width: 0` cell
|
|
75
|
+
* whose content was padded to 200 painted 49 inverse columns at a 60-column
|
|
76
|
+
* terminal, the code never knowing the 49.
|
|
77
|
+
*
|
|
78
|
+
* 512 is deliberate on both sides. Too small leaves the bar short of the right
|
|
79
|
+
* edge on a wide terminal — a visible defect, and the one this fill exists to
|
|
80
|
+
* prevent; 512 is four times the cockpit's own 120-column default and beyond any
|
|
81
|
+
* terminal it is realistically painted into, and a display wide enough to beat it
|
|
82
|
+
* would end the bar early rather than break anything. Too large costs bytes on
|
|
83
|
+
* every painted row: the string is built per cell per frame, and at 512 one
|
|
84
|
+
* grower across ~40 visible rows is ~20 kB of transient string per frame. Only a
|
|
85
|
+
* column that GROWS pays it — a budgeted column is padded to its own declared
|
|
86
|
+
* width instead, which is a handful of blanks.
|
|
87
|
+
*/
|
|
88
|
+
const GROWN_CELL_FILL = 512;
|
|
89
|
+
/** The pad, allocated ONCE and sliced rather than rebuilt per cell. */
|
|
90
|
+
const BLANKS = ' '.repeat(GROWN_CELL_FILL);
|
|
91
|
+
/**
|
|
92
|
+
* A cell's content, PADDED so the row's attribute covers the columns the value
|
|
93
|
+
* does not — GAIA-345, the mechanism behind the continuous bar.
|
|
94
|
+
*
|
|
95
|
+
* CONTENT GENERATION, NOT LAYOUT MEASUREMENT, and the distinction is the whole
|
|
96
|
+
* argument for it: the target is a number the COLUMN DECLARES, never a width
|
|
97
|
+
* measured off the page (spec D4), and the cell resolves to exactly the columns
|
|
98
|
+
* it resolved to before — Yoga is handed the same budget either way. `cellText`
|
|
99
|
+
* and `elideLeft` are the two sanctioned `displayWidth` callers because they
|
|
100
|
+
* mark a cut against a declared width; this is the third and it is the same
|
|
101
|
+
* shape, one step further: it FILLS to a declared width.
|
|
102
|
+
*
|
|
103
|
+
* The two branches are not a style choice, they are what the renderer allows:
|
|
104
|
+
*
|
|
105
|
+
* - A column that cannot grow resolves to exactly `columns`, so its content is
|
|
106
|
+
* padded to exactly `columns` and never overflows. That matters: an
|
|
107
|
+
* OVER-padded fixed cell is an overflowing cell, and the renderer answers an
|
|
108
|
+
* overflow by word-wrapping it. With `height: 1` only the first wrapped line
|
|
109
|
+
* is drawn — measured, a 14-column STATE cell holding ` qualification` plus
|
|
110
|
+
* surplus blanks broke after the leading space and painted BLANK.
|
|
111
|
+
*
|
|
112
|
+
* GAIA-345 (re-entry) — "resolves to exactly `columns`" HOLDS ONLY WHILE THE
|
|
113
|
+
* CELL CANNOT SHRINK, and a `min` is what makes it able to: `textNode` sets
|
|
114
|
+
* `flexShrink: 1` the moment a floor says how far. Such a cell is still padded
|
|
115
|
+
* to its full declared width here — the pad is what the row's highlight rides
|
|
116
|
+
* on, and shortening it to the floor would end the bar early whenever the cell
|
|
117
|
+
* resolved above it. What changed is on the renderer's side: `textNode` now
|
|
118
|
+
* gives every cell that can resolve to an undeclared width, shrinkable ones
|
|
119
|
+
* included, `wrapMode: 'none'`, so the surplus is HARD-CLIPPED instead of
|
|
120
|
+
* word-wrapped and the failure measured just above cannot occur. The two files
|
|
121
|
+
* have to agree about this and the agreement is the fix; see `flexible` in
|
|
122
|
+
* `lib/vnode.ts`.
|
|
123
|
+
* - A column that grows has no such number, so it is over-padded on purpose and
|
|
124
|
+
* the wrap discards the surplus. Measured on this renderer: the blanks reach
|
|
125
|
+
* the cell's own right edge before the wrap takes effect, at every shape this
|
|
126
|
+
* table builds (grower first in the row, grower after a fixed column, and a
|
|
127
|
+
* `max`-capped grower between two).
|
|
128
|
+
*
|
|
129
|
+
* `wrapMode: 'none'` is NOT the alternative for the second branch. Measured: it
|
|
130
|
+
* turns the overflow into the renderer's own `…` truncation, so a value that fits
|
|
131
|
+
* its cell perfectly comes out cut — `Centralize the TUI list…` in a cell wide
|
|
132
|
+
* enough for the whole of it.
|
|
133
|
+
*/
|
|
134
|
+
const cellFill = (text, columns, grows) => grows
|
|
135
|
+
? `${text}${BLANKS}`
|
|
136
|
+
: `${text}${BLANKS.slice(0, Math.max(0, columns - displayWidth(text)))}`;
|
|
137
|
+
/**
|
|
138
|
+
* A row of cells, one per column, placed by YOGA and PAINTED to its edges.
|
|
139
|
+
*
|
|
140
|
+
* A column's `width` is its Yoga budget; the only thing measured here is whether
|
|
141
|
+
* the value outgrew it, so that {@link cellText} can say so. That is the boundary
|
|
142
|
+
* R5/D8 draws: `lib/table.ts`'s `pad()` FILLED the field itself, which is the
|
|
143
|
+
* work Yoga does now. The trailing column declares width 0 and takes what is
|
|
144
|
+
* left.
|
|
145
|
+
*
|
|
146
|
+
* GAIA-345 — TWO THINGS BEYOND THAT MAKE THE ROW'S ATTRIBUTES A CONTINUOUS BAR,
|
|
147
|
+
* and neither is layout:
|
|
148
|
+
*
|
|
149
|
+
* 1. Each cell's content is padded with blanks past its own DECLARED width
|
|
150
|
+
* ({@link cellFill}), so the attribute covers the cell's unused columns as
|
|
151
|
+
* well as its glyphs. This is CONTENT GENERATION, not measurement: the pad
|
|
152
|
+
* is as long as a number the column declares, and no value is ever measured
|
|
153
|
+
* to compute it. `lib/vnode.ts`'s "NOTHING HERE MEASURES A STRING" still
|
|
154
|
+
* binds for LAYOUT, and this changes no layout — a padded cell resolves to
|
|
155
|
+
* exactly the columns it resolved to before.
|
|
156
|
+
* 2. The row is a {@link seamlessRowNode}, and the ` ` that was its Yoga `gap`
|
|
157
|
+
* is a leading blank column of the cell that FOLLOWS it. A gap carries no
|
|
158
|
+
* text and so can carry no attribute; a blank column of a cell does. Every
|
|
159
|
+
* cell but the first is therefore ONE column wider than its column declares,
|
|
160
|
+
* bounds included, and the row's total is unchanged: the same
|
|
161
|
+
* `sum(width) + (n - 1)` the gap produced.
|
|
162
|
+
*
|
|
163
|
+
* The two ids are handed in rather than derived from one another, because an
|
|
164
|
+
* entity id contains hyphens: a cell keyed `<row id>-<column>` would be
|
|
165
|
+
* indistinguishable from a row of some other entity, and "which nodes are the
|
|
166
|
+
* rows" is a question the mouse and every test has to be able to answer.
|
|
167
|
+
*/
|
|
168
|
+
const tableRow = (ids, columns, value, { selected, bold }) => seamlessRowNode(ids.row, ...columns.map((column, index) => {
|
|
169
|
+
// The separator, owned by the cell that follows it. The first cell has
|
|
170
|
+
// none: a row starts at the row's own left edge.
|
|
171
|
+
const lead = index === 0 ? '' : ' ';
|
|
172
|
+
// A `width: 0` column IS a grower — `textNode` gives it `flexGrow: 1` when
|
|
173
|
+
// it declares none — so both spellings of "this cell can outgrow what it
|
|
174
|
+
// declares" ask for the over-padded fill.
|
|
175
|
+
const grows = column.width === 0 || (column.grow ?? 0) > 0;
|
|
176
|
+
// GAIA-345 (pass 3) — THE WIDTH A NON-GROWING COLUMN REALLY DECLARES,
|
|
177
|
+
// which is not always its `width`, and the single number both the fill
|
|
178
|
+
// and the cut below are derived from.
|
|
179
|
+
//
|
|
180
|
+
// `width` is the Yoga BASIS; the bounds are what Yoga resolves it to. A
|
|
181
|
+
// `maxWidth` clamps the basis whether or not the cell may shrink —
|
|
182
|
+
// MEASURED, and it is the only thing the typings could not have said: a
|
|
183
|
+
// `{ width: 14, max: 6 }` column with `flexShrink: 0` resolved to 7
|
|
184
|
+
// columns (6 plus its separator) in a row with 11 columns of surplus, so
|
|
185
|
+
// the column after it started 8 columns earlier than its basis implied.
|
|
186
|
+
// A `minWidth` then wins over both, which is Yoga's own precedence.
|
|
187
|
+
// `max(min ?? 0, min(width, max ?? width))` is that precedence written
|
|
188
|
+
// out, and it is a NO-OP for every column that declares no bound and for
|
|
189
|
+
// every `min` below / `max` above the basis — which is every column this
|
|
190
|
+
// tree ships (none declares a `max` at all).
|
|
191
|
+
//
|
|
192
|
+
// Deriving it HERE is what keeps the two files from disagreeing, and the
|
|
193
|
+
// disagreement was two live defects rather than an untidiness:
|
|
194
|
+
//
|
|
195
|
+
// - `cellFill` padded to the basis while the cell resolved to the cap,
|
|
196
|
+
// so the content overflowed. Such a cell was not `flexible` in
|
|
197
|
+
// `lib/vnode.ts` — a cap is neither a `grow` nor a `min` — so it kept
|
|
198
|
+
// `truncate: true` with no `wrapMode`, the renderer WORD-WRAPPED the
|
|
199
|
+
// overflow and `height: 1` drew the first wrapped line: measured, a
|
|
200
|
+
// `{ width: 14, max: 6 }` cell holding `qualification` painted BLANK,
|
|
201
|
+
// the break falling after the leading separator. Same shape as the
|
|
202
|
+
// `min` case recorded in {@link cellFill}, one more way in.
|
|
203
|
+
// - `cut` was `column.width`, so the ellipsis was marked at the BASIS
|
|
204
|
+
// while the applied cap was the `max`: a value between the two was
|
|
205
|
+
// silently not marked as cut, and one longer than the basis was marked
|
|
206
|
+
// in a column the operator never sees. Widening `flexible` alone fixes
|
|
207
|
+
// the blank cell and leaves THIS half broken, which is why the fix is
|
|
208
|
+
// here and the predicate is only the backstop for `textNode`'s other
|
|
209
|
+
// callers.
|
|
210
|
+
//
|
|
211
|
+
// The `min > width` case is the same expression's other edge and was
|
|
212
|
+
// cosmetic rather than lost text: such a cell resolves to its FLOOR, and
|
|
213
|
+
// padding it to the basis ended the row's highlight 15 columns before the
|
|
214
|
+
// cell did (measured on a `{ width: 5, min: 20 }` probe: 14 inverse
|
|
215
|
+
// columns where the row paints 29). It cannot wrap, because a `min` makes
|
|
216
|
+
// the cell `flexible` and so already gives it `wrapMode: 'none'`.
|
|
217
|
+
//
|
|
218
|
+
// A GROWER keeps its basis untouched. Its resolved width is a share of
|
|
219
|
+
// the row's surplus rather than a clamp of its basis, its fill is
|
|
220
|
+
// {@link GROWN_CELL_FILL} and not a declared number at all, and lowering
|
|
221
|
+
// its basis would change how the OTHER growers of the row divide that
|
|
222
|
+
// surplus — a real rendering change, on the one shape this ticket ships.
|
|
223
|
+
const declared = grows
|
|
224
|
+
? column.width
|
|
225
|
+
: Math.max(column.min ?? 0, Math.min(column.width, column.max ?? column.width));
|
|
226
|
+
const budget = column.width > 0 ? declared + lead.length : 0;
|
|
227
|
+
// GAIA-345 — the width the VALUE is cut against, which is not the Yoga
|
|
228
|
+
// basis once a column can grow. A grower's `width` is where its share
|
|
229
|
+
// STARTS, so cutting there would mark an ellipsis at 14 in a cell Yoga
|
|
230
|
+
// resolved to 32 — the projects and conductors lists both have such a
|
|
231
|
+
// column. What a grower can be cut against is its `max`, if it declares
|
|
232
|
+
// one; where it declares none there is no declared width at all, and the
|
|
233
|
+
// cut falls to the renderer's own clip, unmarked (spec AC 5). `0` is
|
|
234
|
+
// `cellText`'s "no budget", which is what the trailing column always
|
|
235
|
+
// passed.
|
|
236
|
+
const cut = grows ? (column.max ?? 0) : declared;
|
|
237
|
+
return textNode({
|
|
238
|
+
id: ids.cell(column.key),
|
|
239
|
+
content: cellFill(`${lead}${cellText(value(column), cut)}`, budget, grows),
|
|
240
|
+
width: budget,
|
|
241
|
+
// GAIA-345 D3 — the column's declared bounds, handed to Yoga to execute.
|
|
242
|
+
// SPREAD rather than assigned, because `exactOptionalPropertyTypes` makes
|
|
243
|
+
// `min: undefined` a different thing from an absent `min`: the absent one
|
|
244
|
+
// is what means "this column keeps today's behaviour", and `textNode` is
|
|
245
|
+
// where that turns into `flexShrink: 0` and no bound at all. Each bound
|
|
246
|
+
// carries the separator too, or a `min` would floor the cell one column
|
|
247
|
+
// below the columns its own column declares.
|
|
248
|
+
...(column.min === undefined ? {} : { min: column.min + lead.length }),
|
|
249
|
+
...(column.max === undefined ? {} : { max: column.max + lead.length }),
|
|
250
|
+
...(column.grow === undefined ? {} : { grow: column.grow }),
|
|
251
|
+
selected,
|
|
252
|
+
// GAIA-345 D5 — STATED ON EVERY ROW, off included, for the reason
|
|
253
|
+
// `lib/vnode.ts` gives about `selected` right beside it: `applyProps`
|
|
254
|
+
// writes only what the next tree declares, so a `bold` emitted for the
|
|
255
|
+
// header alone would be set once and never taken off the row that
|
|
256
|
+
// inherited its node.
|
|
257
|
+
bold,
|
|
258
|
+
});
|
|
259
|
+
}));
|
|
260
|
+
/** A column-driven list's row ids in selection order — see `lib/vnode.ts`'s
|
|
261
|
+
* `rowNodeId`. Beside {@link listTableTree}, which keys the rows under it, and
|
|
262
|
+
* reading the SAME `noun` off the same configuration (GAIA-345 D2). */
|
|
263
|
+
export const listRowIds = (table, rows) => rows.map((row) => rowNodeId(table.noun, row.id));
|
|
264
|
+
/**
|
|
265
|
+
* GAIA-340 D1 — a column-driven list as the renderable tree a screen hands over.
|
|
266
|
+
*
|
|
267
|
+
* One node per row, keyed `<noun>-row-<entity id>` (D4/R4) and never by position:
|
|
268
|
+
* a list re-sorts on every search, page and reload, and a positional key would
|
|
269
|
+
* move one ticket's cells onto another ticket's node with nothing on screen to
|
|
270
|
+
* say so. The cells are keyed within their row by the COLUMN's own `key`, which
|
|
271
|
+
* is what makes the whole set unique by construction.
|
|
272
|
+
*
|
|
273
|
+
* MEASURED, so the choice is not re-litigated: `TextTableRenderable` was tried and
|
|
274
|
+
* rejected. It is ONE renderable whose rows are internal cell views, so no row can
|
|
275
|
+
* carry a `props.id` — the identity `Kernel/mount.ts` patches by, and the identity
|
|
276
|
+
* the mouse will need when it moves onto the renderables — and its column widths
|
|
277
|
+
* are computed from the CONTENT, so the columns would shift every time the page's
|
|
278
|
+
* data changes.
|
|
279
|
+
*
|
|
280
|
+
* THE HIGHLIGHT IS A FULL-WIDTH BAR, and GAIA-345 is where that was decided.
|
|
281
|
+
* Until then this file argued the opposite — that a bar "needs the row's blank
|
|
282
|
+
* columns painted, and the only ways to paint them are a padded string or a fixed
|
|
283
|
+
* colour, and both are worse". The operator overruled it on sight: a highlight
|
|
284
|
+
* covering only the glyphs came out as one fragment per cell, 58 of 120 columns
|
|
285
|
+
* in eight pieces, and read as "only the title of the row is active". The two
|
|
286
|
+
* costs that argument weighed are not the ones that were paid, either. The fill
|
|
287
|
+
* is a blank run as long as a number the COLUMN DECLARES, so no value is measured
|
|
288
|
+
* to build it and `lib/width.ts` is not resurrected under a new name (see
|
|
289
|
+
* {@link tableRow}); and the paint is still the operator's own inverse attribute,
|
|
290
|
+
* so no colour is frozen and the GAIA-326 regression stays fixed. What the
|
|
291
|
+
* argument got right is that the row's blank columns have to be painted — it was
|
|
292
|
+
* wrong that the only ways of doing it were the two bad ones.
|
|
293
|
+
*
|
|
294
|
+
* GAIA-340 Task 8 moved it HERE from `Screen/list-screen.ts`, where Task 7 first
|
|
295
|
+
* wrote it, for the reason {@link ListColumn} gives above it: the run table has a
|
|
296
|
+
* SECOND consumer in the ticket detail's Runs tab (GAIA-255 AC-6 renders a run
|
|
297
|
+
* the same way on both screens), that consumer is a `Component/`, and
|
|
298
|
+
* `tests/boundary.test.ts` G5 forbids anything but an entity-local `Screen/` the
|
|
299
|
+
* `Screen/` directory. Duplicating the renderer instead would have made "one
|
|
300
|
+
* renderer, so the two screens cannot disagree" false.
|
|
301
|
+
*/
|
|
302
|
+
export function listTableTree(table, rows, ctx, { selectedIndex }) {
|
|
303
|
+
const { noun, columns, emptyMessage, header = true } = table;
|
|
304
|
+
return columnNode(`${noun}-table`,
|
|
305
|
+
// GAIA-345 D5/D6 — the header is BOLD, which is what distinguishes it from a
|
|
306
|
+
// data row, and it is optional because one list is not a record set: the
|
|
307
|
+
// project picker asks a question and configures it away.
|
|
308
|
+
...(header
|
|
309
|
+
? [
|
|
310
|
+
tableRow({
|
|
311
|
+
row: `${noun}-header`,
|
|
312
|
+
cell: (key) => `${noun}-header-${key}`,
|
|
313
|
+
}, columns, (column) => column.label, { selected: false, bold: true }),
|
|
314
|
+
]
|
|
315
|
+
: []), ...(rows.length === 0
|
|
316
|
+
? [textNode({ id: `${noun}-empty`, content: emptyMessage })]
|
|
317
|
+
: rows.map((row, index) => tableRow({
|
|
318
|
+
row: rowNodeId(noun, row.id),
|
|
319
|
+
cell: (key) => `${noun}-cell-${row.id}-${key}`,
|
|
320
|
+
}, columns, (column) => column.get(row, ctx), { selected: index === selectedIndex, bold: false }))));
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* GAIA-345 D7 — the SAME list for the surface that has no layout:
|
|
324
|
+
* `gaia ui GAIA-nnn | cat`.
|
|
325
|
+
*
|
|
326
|
+
* GAIA-340 F5 keeps the pipe's own output, and this keeps its BYTES while
|
|
327
|
+
* retiring the last per-entity column→row mapper (`run-table`'s `runTableRows`):
|
|
328
|
+
* each cell carries the value and the column's own budget exactly as that emitted
|
|
329
|
+
* them, and `Component/section-list`'s `lineText` still joins them with ONE
|
|
330
|
+
* space. A configuration therefore feeds both surfaces, which is what makes "one
|
|
331
|
+
* component renders every list" true of the run table rather than of its terminal
|
|
332
|
+
* half only.
|
|
333
|
+
*
|
|
334
|
+
* The values are RAW here where {@link listTableTree} runs them through
|
|
335
|
+
* {@link cellText}. That is not an oversight and not a second rendering rule: the
|
|
336
|
+
* ellipsis exists because a Yoga budget cuts silently, and a pipe has no budget
|
|
337
|
+
* to cut against — it prints whatever it is given, at whatever width the reader's
|
|
338
|
+
* terminal or file happens to be. Cutting here would be a cut nobody asked for.
|
|
339
|
+
*
|
|
340
|
+
* The header follows `table.header`, so a headerless list prints headerless and
|
|
341
|
+
* the two surfaces cannot disagree about whether this list has one.
|
|
342
|
+
*/
|
|
343
|
+
export function listTableRows(table, rows, ctx) {
|
|
344
|
+
const line = (value) => ({
|
|
345
|
+
cells: table.columns.map((column) => {
|
|
346
|
+
const raw = value(column);
|
|
347
|
+
return {
|
|
348
|
+
text: raw === null || raw === undefined ? '' : String(raw),
|
|
349
|
+
width: column.width,
|
|
350
|
+
};
|
|
351
|
+
}),
|
|
352
|
+
});
|
|
353
|
+
const head = table.header === false ? [] : [line((column) => column.label)];
|
|
354
|
+
if (rows.length === 0)
|
|
355
|
+
return [...head, table.emptyMessage];
|
|
356
|
+
return [
|
|
357
|
+
...head,
|
|
358
|
+
...rows.map((row) => line((column) => column.get(row, ctx))),
|
|
359
|
+
];
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Read one page of a collection and map it to rows.
|
|
363
|
+
*
|
|
364
|
+
* `mapRows` receives `data` AND `included` so each slice keeps its own sideload
|
|
365
|
+
* indexing — the ticket list indexes every include by id, the run list builds a
|
|
366
|
+
* ticket-only map — without this function knowing either.
|
|
367
|
+
*/
|
|
368
|
+
export async function loadPage(client, path, params, mapRows, page, window) {
|
|
369
|
+
const doc = (await client.get(path, params));
|
|
370
|
+
return {
|
|
371
|
+
rows: mapRows(doc?.data ?? [], doc?.included ?? []),
|
|
372
|
+
hasNext: Boolean(doc?.links?.next),
|
|
373
|
+
page,
|
|
374
|
+
total: readTotal(doc),
|
|
375
|
+
window,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** The minimum shape a conductor row needs to render. */
|
|
2
|
+
export interface ConductorRowInput {
|
|
3
|
+
label: string;
|
|
4
|
+
machineId: string | undefined;
|
|
5
|
+
project: string;
|
|
6
|
+
derivedStatus: string;
|
|
7
|
+
ageLabel: string;
|
|
8
|
+
workspaceRoot: string;
|
|
9
|
+
lastPid: number | undefined;
|
|
10
|
+
/** Whether this conductor's workspace exists on THIS machine. */
|
|
11
|
+
isLocal: boolean;
|
|
12
|
+
/** Whether an action on this row is in flight. */
|
|
13
|
+
busy: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* One line per conductor: who, where, alive, since when, and its process.
|
|
17
|
+
*
|
|
18
|
+
* A non-local row is marked rather than hidden. The control plane is
|
|
19
|
+
* user-scoped, not machine-scoped, so a user's other machines' conductors are
|
|
20
|
+
* legitimately in this list — and silently dropping them would make the cockpit
|
|
21
|
+
* disagree with `gaia conductor ls` for no visible reason. Marking them says
|
|
22
|
+
* why their actions are inert here: a pid from another machine means nothing on
|
|
23
|
+
* this one.
|
|
24
|
+
*/
|
|
25
|
+
export declare function conductorRows(conductors: ConductorRowInput[]): string[];
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { pad } from '../../../../lib/format.js';
|
|
2
|
+
/** How much width the workspace column gets before it is elided. */
|
|
3
|
+
const PATH_WIDTH = 30;
|
|
4
|
+
/**
|
|
5
|
+
* How much width the machine_id column gets before it is elided.
|
|
6
|
+
*
|
|
7
|
+
* A `machine_id` is COMPOSED (`${user_id}-${machine_id}-${project}`), so on a
|
|
8
|
+
* user-scoped control plane every row repeats the same uuid at the head. It is
|
|
9
|
+
* therefore elided from the left like the path: the tail is the only part that
|
|
10
|
+
* tells one conductor from another.
|
|
11
|
+
*/
|
|
12
|
+
const MACHINE_ID_WIDTH = 20;
|
|
13
|
+
/**
|
|
14
|
+
* One line per conductor: who, where, alive, since when, and its process.
|
|
15
|
+
*
|
|
16
|
+
* A non-local row is marked rather than hidden. The control plane is
|
|
17
|
+
* user-scoped, not machine-scoped, so a user's other machines' conductors are
|
|
18
|
+
* legitimately in this list — and silently dropping them would make the cockpit
|
|
19
|
+
* disagree with `gaia conductor ls` for no visible reason. Marking them says
|
|
20
|
+
* why their actions are inert here: a pid from another machine means nothing on
|
|
21
|
+
* this one.
|
|
22
|
+
*/
|
|
23
|
+
export function conductorRows(conductors) {
|
|
24
|
+
if (conductors.length === 0)
|
|
25
|
+
return ['No conductors on this control plane.'];
|
|
26
|
+
return conductors.map((c) => {
|
|
27
|
+
const name = c.label !== '' ? c.label : (c.machineId ?? '');
|
|
28
|
+
const pid = c.lastPid === undefined ? '-' : String(c.lastPid);
|
|
29
|
+
const marks = [c.busy ? '…' : '', c.isLocal ? '' : 'elsewhere']
|
|
30
|
+
.filter((m) => m !== '')
|
|
31
|
+
.join(' ');
|
|
32
|
+
return [
|
|
33
|
+
pad(name, 20),
|
|
34
|
+
// Its OWN column, not merely the label's fallback (AC-1, spec D2): two
|
|
35
|
+
// conductors of one repo share a project and a workspace_root, and the
|
|
36
|
+
// machine_id is the only thing that separates them.
|
|
37
|
+
pad(elideLeft(c.machineId ?? '', MACHINE_ID_WIDTH), MACHINE_ID_WIDTH),
|
|
38
|
+
pad(c.project, 14),
|
|
39
|
+
pad(c.derivedStatus, 9),
|
|
40
|
+
pad(c.ageLabel, 8),
|
|
41
|
+
pad(elideLeft(c.workspaceRoot, PATH_WIDTH), PATH_WIDTH),
|
|
42
|
+
pad(pid, 7),
|
|
43
|
+
marks,
|
|
44
|
+
]
|
|
45
|
+
.join(' ')
|
|
46
|
+
.trimEnd();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Shorten a path from the LEFT, keeping its end.
|
|
51
|
+
*
|
|
52
|
+
* Conductor workspaces share a long common head (`/home/<user>/projects/…`) and
|
|
53
|
+
* differ at the tail, which is also the part that identifies the checkout.
|
|
54
|
+
* Eliding the right would leave every row looking identical.
|
|
55
|
+
*/
|
|
56
|
+
function elideLeft(path, width) {
|
|
57
|
+
if (path.length <= width)
|
|
58
|
+
return path;
|
|
59
|
+
return `…${path.slice(path.length - (width - 1))}`;
|
|
60
|
+
}
|