@gaia-ai/addon-gaia-ui 0.13.0 → 0.13.1
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/Entity/statistics/Data/statistics.d.ts +18 -13
- package/dist/src/Entity/statistics/Data/statistics.js +35 -11
- package/dist/src/Entity/statistics/render.js +13 -11
- package/dist/src/Entity/team/Screen/team-users.js +16 -2
- package/dist/src/Entity/ticket/Screen/ticket-detail.d.ts +9 -0
- package/dist/src/Entity/ticket/Screen/ticket-detail.js +85 -12
- package/dist/src/Entity/workspace/Screen/workspace-picker.js +4 -4
- package/dist/src/Extension/contract.d.ts +16 -0
- package/dist/src/types.d.ts +2 -2
- package/package.json +2 -2
|
@@ -39,18 +39,31 @@ export interface SeriesFigureValue {
|
|
|
39
39
|
days: StatisticDay[];
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
|
-
* A `ratio` figure's value:
|
|
42
|
+
* A `ratio` figure's value: the quotient, and the two factors behind it.
|
|
43
43
|
*
|
|
44
|
-
* The
|
|
45
|
-
* server-computed copy would be a second source of truth
|
|
46
|
-
*
|
|
47
|
-
*
|
|
44
|
+
* The division was CLIENT-SIDE until GAIA-481 D3, on the reasoning that a
|
|
45
|
+
* server-computed copy would be a second source of truth. The web Stats tabs
|
|
46
|
+
* reversed that reasoning: a second SURFACE would have had to re-implement
|
|
47
|
+
* the same rule in PHP, so the division and its zero-denominator case moved
|
|
48
|
+
* to the server instead. `ratio` is therefore READ and rendered, never
|
|
49
|
+
* recomputed here — and it arrives already formatted, because the empty
|
|
50
|
+
* value is part of the rule rather than a presentation choice each surface
|
|
51
|
+
* makes for itself.
|
|
52
|
+
*
|
|
53
|
+
* `numerator` and `denominator` still travel beside it, unchanged.
|
|
48
54
|
*/
|
|
49
55
|
export interface RatioFigureValue {
|
|
50
56
|
generated: string;
|
|
57
|
+
ratio: RatioQuotients;
|
|
51
58
|
numerator: WindowsFigureValue;
|
|
52
59
|
denominator: WindowsFigureValue;
|
|
53
60
|
}
|
|
61
|
+
/** A ratio's derived value per trailing window: two decimals, or `—`. */
|
|
62
|
+
export interface RatioQuotients {
|
|
63
|
+
day: string;
|
|
64
|
+
week: string;
|
|
65
|
+
month: string;
|
|
66
|
+
}
|
|
54
67
|
export type FigureValue = WindowsFigureValue | SeriesFigureValue | RatioFigureValue;
|
|
55
68
|
/** One figure discovery names: which blocks the caller may render (spec D5). */
|
|
56
69
|
export interface StatisticListing {
|
|
@@ -132,11 +145,3 @@ export declare function loadStatisticValue(client: DropshClient, statisticId: st
|
|
|
132
145
|
* its own (spec D5). Figures already arrive weight-ordered from discovery.
|
|
133
146
|
*/
|
|
134
147
|
export declare function loadGroupStatistics(client: DropshClient, scopeId: string): Promise<GroupStatisticsResult>;
|
|
135
|
-
/** Closed-per-run-shaped ratio, for each trailing window, from two totals. */
|
|
136
|
-
export declare function ratioOf(numerator: StatisticsWindows, denominator: StatisticsWindows): {
|
|
137
|
-
day: number | null;
|
|
138
|
-
week: number | null;
|
|
139
|
-
month: number | null;
|
|
140
|
-
};
|
|
141
|
-
/** Format a ratio value for the screen: two decimals, or `—`. */
|
|
142
|
-
export declare const formatRatio: (value: number | null) => string;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { DrupalJsonApiParams } from 'drupal-jsonapi-params';
|
|
2
|
+
/**
|
|
3
|
+
* What a ratio window reads as when there is nothing to divide by.
|
|
4
|
+
*
|
|
5
|
+
* The server owns this rule and sends the character; this constant exists
|
|
6
|
+
* only so a response that carries no quotient at all degrades to the same
|
|
7
|
+
* thing rather than to an empty cell.
|
|
8
|
+
*/
|
|
9
|
+
const RATIO_EMPTY = '—';
|
|
2
10
|
/** How an `id: null` breakdown row is named on screen. */
|
|
3
11
|
export const UNATTRIBUTED_LABEL = '— unattributed';
|
|
4
12
|
const num = (value) => typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
@@ -43,6 +51,23 @@ function windowsFigureOf(attributes) {
|
|
|
43
51
|
...(rows ? { rows } : {}),
|
|
44
52
|
};
|
|
45
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* A ratio figure's quotients, as the server formatted them (GAIA-481 D3).
|
|
56
|
+
*
|
|
57
|
+
* A control plane that predates the move sends no `ratio` at all, and this
|
|
58
|
+
* reads the empty value for every window rather than throwing or dividing a
|
|
59
|
+
* replacement: an older server is a missing figure, not a wrong one, and the
|
|
60
|
+
* one thing this must not do is quietly reintroduce the division it replaced.
|
|
61
|
+
*/
|
|
62
|
+
function quotientsOf(value) {
|
|
63
|
+
const source = (value ?? {});
|
|
64
|
+
const windowOf = (key) => typeof source[key] === 'string' ? source[key] : RATIO_EMPTY;
|
|
65
|
+
return {
|
|
66
|
+
day: windowOf('day'),
|
|
67
|
+
week: windowOf('week'),
|
|
68
|
+
month: windowOf('month'),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
46
71
|
function figureValueOf(attributes) {
|
|
47
72
|
if (Array.isArray(attributes.days)) {
|
|
48
73
|
return {
|
|
@@ -56,6 +81,7 @@ function figureValueOf(attributes) {
|
|
|
56
81
|
attributes.denominator !== null) {
|
|
57
82
|
return {
|
|
58
83
|
generated: typeof attributes.generated === 'string' ? attributes.generated : '',
|
|
84
|
+
ratio: quotientsOf(attributes.ratio),
|
|
59
85
|
numerator: windowsFigureOf(attributes.numerator),
|
|
60
86
|
denominator: windowsFigureOf(attributes.denominator),
|
|
61
87
|
};
|
|
@@ -198,14 +224,12 @@ export async function loadGroupStatistics(client, scopeId) {
|
|
|
198
224
|
}
|
|
199
225
|
return { kind: 'ok', figures };
|
|
200
226
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
/** Format a ratio value for the screen: two decimals, or `—`. */
|
|
211
|
-
export const formatRatio = (value) => value === null ? '—' : value.toFixed(2);
|
|
227
|
+
// GAIA-481 D3 — `ratioOf` and `formatRatio` were exported from here, and are
|
|
228
|
+
// gone. A `ratio` figure's quotient is the server's now
|
|
229
|
+
// (`RatioFigureValue.ratio`), so this module deliberately offers no ratio
|
|
230
|
+
// arithmetic for a caller to reach for: a shared helper sitting beside the
|
|
231
|
+
// server's own value is exactly how the second implementation would come
|
|
232
|
+
// back. The team Users screen still divides, because it derives a PER-MEMBER
|
|
233
|
+
// quotient out of two breakdown figures' rows and no configured figure
|
|
234
|
+
// produces that one; it owns that arithmetic privately rather than sharing
|
|
235
|
+
// it from here.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { statsPanelRows, } from '../../Component/stats-panel/index.js';
|
|
2
|
-
import { formatRatio, ratioOf } from './Data/statistics.js';
|
|
3
2
|
// GAIA-456 spec D2/D7 — mapping the four configured display kinds onto the
|
|
4
3
|
// renderers the TUI already had: `windows` is the totals row GAIA-256 drew,
|
|
5
4
|
// `breakdown` is the label-by-window table, `series` is the seven-day strip
|
|
@@ -24,13 +23,19 @@ function seriesLines(days) {
|
|
|
24
23
|
const counts = ` ${'Count'.padEnd(LABEL - 2, ' ')}${days.map((row) => cell(String(row.count))).join('')}`;
|
|
25
24
|
return [weekdays, dates, counts];
|
|
26
25
|
}
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
/**
|
|
27
|
+
* A ratio figure's quotient per trailing window, as the SERVER divided it.
|
|
28
|
+
*
|
|
29
|
+
* The values arrive already formatted (GAIA-481 D3), so this lays them out
|
|
30
|
+
* and does nothing else — no division and no em-dash rule, both of which are
|
|
31
|
+
* the server's now and would otherwise be a terminal-only second opinion on
|
|
32
|
+
* the same figure the browser renders.
|
|
33
|
+
*/
|
|
34
|
+
function ratioLines(ratios) {
|
|
30
35
|
return [
|
|
31
|
-
` ${WINDOWS[0]} ${
|
|
32
|
-
` ${WINDOWS[1]} ${
|
|
33
|
-
` ${WINDOWS[2]} ${
|
|
36
|
+
` ${WINDOWS[0]} ${ratios.day}`,
|
|
37
|
+
` ${WINDOWS[1]} ${ratios.week}`,
|
|
38
|
+
` ${WINDOWS[2]} ${ratios.month}`,
|
|
34
39
|
];
|
|
35
40
|
}
|
|
36
41
|
/** One figure's own section: its label as the heading, its display kind's lines. */
|
|
@@ -41,10 +46,7 @@ export function figureSection({ listing, value }) {
|
|
|
41
46
|
return { heading, lines: seriesLines(value.days) };
|
|
42
47
|
case 'ratio': {
|
|
43
48
|
const ratio = value;
|
|
44
|
-
return {
|
|
45
|
-
heading,
|
|
46
|
-
lines: ratioLines(ratio.numerator.total, ratio.denominator.total),
|
|
47
|
-
};
|
|
49
|
+
return { heading, lines: ratioLines(ratio.ratio) };
|
|
48
50
|
}
|
|
49
51
|
case 'breakdown': {
|
|
50
52
|
const windows = value;
|
|
@@ -4,7 +4,7 @@ import { ListCursor } from '../../../lib/cursor.js';
|
|
|
4
4
|
import { columnNode } from '../../../lib/vnode.js';
|
|
5
5
|
import { AsyncScreen } from '../../../Screen/async-screen.js';
|
|
6
6
|
import { listRowIds, listTableTree } from '../../collection.js';
|
|
7
|
-
import {
|
|
7
|
+
import { loadGroupStatistics, } from '../../statistics/Data/statistics.js';
|
|
8
8
|
import { listUsers, TEAM_MEMBER_TABLE, } from '../../user/Data/users.js';
|
|
9
9
|
import { listTeamMemberIds } from '../Data/members.js';
|
|
10
10
|
import { TeamsScreen } from './teams-screen.js';
|
|
@@ -48,6 +48,20 @@ function rowsById(statistics, statisticId) {
|
|
|
48
48
|
}
|
|
49
49
|
return map;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* This screen's own closed-per-run, for ONE member over the 30-day window.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately local and unexported (GAIA-481 D3). A `ratio` FIGURE is
|
|
55
|
+
* divided by the server, and this is not one: it is derived per member from
|
|
56
|
+
* two breakdown figures' rows, and no configured figure produces it. Keeping
|
|
57
|
+
* it here rather than in the shared statistics module is what stops it being
|
|
58
|
+
* mistaken for the general "how a ratio is computed" helper and reached for
|
|
59
|
+
* by a surface that should be reading the server's value instead.
|
|
60
|
+
*
|
|
61
|
+
* The rule matches the server's so the two columns agree on screen: two
|
|
62
|
+
* decimals, and an em-dash when there is nothing to divide by.
|
|
63
|
+
*/
|
|
64
|
+
const perMemberRatio = (numerator, denominator) => denominator > 0 ? (numerator / denominator).toFixed(2) : '—';
|
|
51
65
|
/** One member's row: identity from the collection, figures from the two per-user breakdowns. */
|
|
52
66
|
function rowOf(user, ticketsById, runsById) {
|
|
53
67
|
const tickets30d = ticketsById.get(user.id)?.month ?? 0;
|
|
@@ -57,7 +71,7 @@ function rowOf(user, ticketsById, runsById) {
|
|
|
57
71
|
label: user.label,
|
|
58
72
|
tickets30d,
|
|
59
73
|
runs30d,
|
|
60
|
-
closedPerRun30d:
|
|
74
|
+
closedPerRun30d: perMemberRatio(tickets30d, runs30d),
|
|
61
75
|
};
|
|
62
76
|
}
|
|
63
77
|
/**
|
|
@@ -251,6 +251,15 @@ export declare class TicketDetailScreen extends AsyncScreen<TicketDetailDocument
|
|
|
251
251
|
* operator saw; a table whose `rows()` throws resolves to the error branch and
|
|
252
252
|
* simply has no row to activate.
|
|
253
253
|
*/
|
|
254
|
+
/**
|
|
255
|
+
* GAIA-500 — `l`/`t`'s shared plumbing: resolve the active table's selected
|
|
256
|
+
* row and ask its `copyLocal`/`copyTunnel`, returning `undefined` when there
|
|
257
|
+
* is no active table, the table does not implement that side, or the row has
|
|
258
|
+
* nothing to offer — in every one of which cases the key is left UNHANDLED
|
|
259
|
+
* (the caller falls through) rather than reporting a copy that did not
|
|
260
|
+
* happen, exactly as `l`/`t` do nothing on every tab that is not a table.
|
|
261
|
+
*/
|
|
262
|
+
private copyActiveTableRow;
|
|
254
263
|
private activateTableRow;
|
|
255
264
|
/** Tests and the busy indicator: the row ids whose activation is in flight. */
|
|
256
265
|
activatingRowIds(): string[];
|
|
@@ -428,24 +428,49 @@ export class TicketDetailScreen extends AsyncScreen {
|
|
|
428
428
|
// `selectTopTab` seam would be dead code on the key path. The wrap comes from
|
|
429
429
|
// `TAB_LEVELS.ticket` now, the same wrap every other level gets.
|
|
430
430
|
//
|
|
431
|
-
// GAIA-326 — the same copy ladder as the ticket LIST (`y` the identifier,
|
|
432
|
-
// `Y` the link), acting on the ticket that is open. This screen has no text
|
|
433
|
-
// field, so the keys need no guard beyond the `ready`/`full` gate above.
|
|
434
|
-
if (input === 'y' || input === 'Y') {
|
|
435
|
-
this.status = this.copyOpenTicket(input === 'Y', context);
|
|
436
|
-
context.requestDraw();
|
|
437
|
-
return true;
|
|
438
|
-
}
|
|
439
431
|
const view = this.currentView(context);
|
|
440
432
|
// GAIA-475 UX — `s` STARTS and `x` STOPS the selected row's action on a
|
|
441
433
|
// TABLE tab, two distinct keys because Enter reads as "expand" on a detail
|
|
442
434
|
// screen and one key doing both is a confusing toggle. Only when a table is
|
|
443
435
|
// the active contribution, so `s`/`x` are untouched everywhere else.
|
|
444
436
|
const onTable = this.activeContribution(context)?.type === 'table';
|
|
437
|
+
// GAIA-326 — the same copy ladder as the ticket LIST (`y` the identifier,
|
|
438
|
+
// `Y` the link), acting on the ticket that is open. NOT on a table tab
|
|
439
|
+
// (GAIA-500): a table's own rows have their own copy (`l`/`t`), a
|
|
440
|
+
// different target entirely, and `y`/`Y` copying the open ticket instead —
|
|
441
|
+
// a fact about the SCREEN, unrelated to whatever row is selected — read as
|
|
442
|
+
// two unrelated "copy" meanings live at once, confusing enough to report.
|
|
443
|
+
// This screen has no text field, so the keys need no guard beyond the
|
|
444
|
+
// `ready`/`full` gate above.
|
|
445
|
+
if (!onTable && (input === 'y' || input === 'Y')) {
|
|
446
|
+
this.status = this.copyOpenTicket(input === 'Y', context);
|
|
447
|
+
context.requestDraw();
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
445
450
|
if (onTable && input === 's' && view.selectable > 0)
|
|
446
451
|
return this.activate(this.selectedEntry, context, 'activate');
|
|
447
452
|
if (onTable && input === 'x' && view.selectable > 0)
|
|
448
453
|
return this.activate(this.selectedEntry, context, 'deactivate');
|
|
454
|
+
// GAIA-500 — `l` copies the selected row's LOCAL address, `t` the address
|
|
455
|
+
// reached through the TUNNEL: the same two-distinct-keys shape as `s`/`x`,
|
|
456
|
+
// for the same reason — one key meaning "copy whichever of two addresses
|
|
457
|
+
// happens to apply" is the confusing toggle this avoids.
|
|
458
|
+
if (onTable && input === 'l' && view.selectable > 0) {
|
|
459
|
+
const copied = this.copyActiveTableRow(context, 'local');
|
|
460
|
+
if (copied !== undefined) {
|
|
461
|
+
this.status = copied;
|
|
462
|
+
context.requestDraw();
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (onTable && input === 't' && view.selectable > 0) {
|
|
467
|
+
const copied = this.copyActiveTableRow(context, 'tunnel');
|
|
468
|
+
if (copied !== undefined) {
|
|
469
|
+
this.status = copied;
|
|
470
|
+
context.requestDraw();
|
|
471
|
+
return true;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
449
474
|
if ((input === 'enter' || input === ' ') && view.selectable > 0)
|
|
450
475
|
return this.activate(this.selectedEntry, context);
|
|
451
476
|
// GAIA-369 AC-7 — "HANDLED" MEANS THE SELECTION MOVED, and that is the whole
|
|
@@ -614,20 +639,38 @@ export class TicketDetailScreen extends AsyncScreen {
|
|
|
614
639
|
const view = this.currentView(context);
|
|
615
640
|
const arrows = view.selectable > 1 ? 'select' : 'scroll';
|
|
616
641
|
// GAIA-346 — `Tab section`; kernel owns Esc/q. GAIA-373 — section via registry.
|
|
642
|
+
const contribution = this.activeContribution(context);
|
|
643
|
+
const onTable = contribution?.type === 'table';
|
|
617
644
|
const actions = [
|
|
618
645
|
actionItem('detail-tab', 'Tab', 'section'),
|
|
619
646
|
actionItem('detail-edit', 'e', 'edit'),
|
|
620
|
-
actionItem('detail-copy', 'y', 'copy'),
|
|
621
|
-
actionItem('detail-link', 'Y', 'link'),
|
|
622
|
-
actionItem('detail-arrows', '↑↓', arrows),
|
|
623
647
|
];
|
|
648
|
+
// GAIA-500 — NOT on a table tab: a table's own rows carry their own copy
|
|
649
|
+
// (`l`/`t` below), a different target than the open ticket, and having
|
|
650
|
+
// both live on the same screen at once is what read as confusing enough
|
|
651
|
+
// to report — named `copy id`/`copy link` here rather than the bare
|
|
652
|
+
// `copy`/`link` this carried before, so it cannot misread as this row's
|
|
653
|
+
// copy on the one tab where it is not.
|
|
654
|
+
if (!onTable) {
|
|
655
|
+
actions.push(actionItem('detail-copy', 'y', 'copy id'));
|
|
656
|
+
actions.push(actionItem('detail-link', 'Y', 'copy link'));
|
|
657
|
+
}
|
|
658
|
+
actions.push(actionItem('detail-arrows', '↑↓', arrows));
|
|
624
659
|
if (view.selectable > 0) {
|
|
625
660
|
// GAIA-475 — a table tab offers its action under a dedicated key `s`, so
|
|
626
661
|
// the footer names it rather than leaving the operator to guess Enter does
|
|
627
662
|
// something other than expand.
|
|
628
|
-
if (
|
|
663
|
+
if (onTable && contribution) {
|
|
629
664
|
actions.push(actionItem('detail-share', 's', 'start'));
|
|
630
665
|
actions.push(actionItem('detail-unshare', 'x', 'stop'));
|
|
666
|
+
// GAIA-500 — the row's own copy, distinct from `y`/`Y` above (which a
|
|
667
|
+
// table tab no longer even offers) for the reason `s`/`x` already read
|
|
668
|
+
// "start"/"stop" rather than "share" twice — which of two things a key
|
|
669
|
+
// copies is the part worth saying.
|
|
670
|
+
if (contribution.copyLocal)
|
|
671
|
+
actions.push(actionItem('detail-copy-local', 'l', 'copy local'));
|
|
672
|
+
if (contribution.copyTunnel)
|
|
673
|
+
actions.push(actionItem('detail-copy-tunnel', 't', 'copy tunnel'));
|
|
631
674
|
}
|
|
632
675
|
else {
|
|
633
676
|
actions.push(this.activeSectionId(context) === 'runs' &&
|
|
@@ -740,6 +783,36 @@ export class TicketDetailScreen extends AsyncScreen {
|
|
|
740
783
|
* operator saw; a table whose `rows()` throws resolves to the error branch and
|
|
741
784
|
* simply has no row to activate.
|
|
742
785
|
*/
|
|
786
|
+
/**
|
|
787
|
+
* GAIA-500 — `l`/`t`'s shared plumbing: resolve the active table's selected
|
|
788
|
+
* row and ask its `copyLocal`/`copyTunnel`, returning `undefined` when there
|
|
789
|
+
* is no active table, the table does not implement that side, or the row has
|
|
790
|
+
* nothing to offer — in every one of which cases the key is left UNHANDLED
|
|
791
|
+
* (the caller falls through) rather than reporting a copy that did not
|
|
792
|
+
* happen, exactly as `l`/`t` do nothing on every tab that is not a table.
|
|
793
|
+
*/
|
|
794
|
+
copyActiveTableRow(context, side) {
|
|
795
|
+
const contribution = this.activeContribution(context);
|
|
796
|
+
if (contribution?.type !== 'table')
|
|
797
|
+
return;
|
|
798
|
+
const copyField = side === 'local' ? contribution.copyLocal : contribution.copyTunnel;
|
|
799
|
+
if (!copyField)
|
|
800
|
+
return;
|
|
801
|
+
const nowSeconds = context.nowSeconds?.() ?? Math.floor(Date.now() / 1000);
|
|
802
|
+
const resolved = this.tableView(contribution, context, {
|
|
803
|
+
expanded: new Set(),
|
|
804
|
+
nowSeconds,
|
|
805
|
+
});
|
|
806
|
+
if ('panelSections' in resolved)
|
|
807
|
+
return;
|
|
808
|
+
const row = resolved.panelTable.rows[this.selectedEntry];
|
|
809
|
+
if (!row)
|
|
810
|
+
return;
|
|
811
|
+
const value = copyField(row);
|
|
812
|
+
if (value === undefined)
|
|
813
|
+
return;
|
|
814
|
+
return copyThrough(context.copyToClipboard, value, value, 'nothing to copy');
|
|
815
|
+
}
|
|
743
816
|
async activateTableRow(table, index, context, intent) {
|
|
744
817
|
const nowSeconds = context.nowSeconds?.() ?? Math.floor(Date.now() / 1000);
|
|
745
818
|
const resolved = this.tableView(table, context, {
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
//
|
|
8
8
|
// The switch does three things, in order, on Enter: (1) refuse silently when
|
|
9
9
|
// the choice is already active — a write and a restart over "the same" would
|
|
10
|
-
// be a no-op with side effects; (2) write the selection through
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
10
|
+
// be a no-op with side effects; (2) write the selection through `@gaia-ai/core`'s
|
|
11
|
+
// `workspaceOwner` + `writeWorkspaceIdSelection`, the single ownership-and-write
|
|
12
|
+
// path, so a manual edit and a cockpit switch commit through the identical path
|
|
13
|
+
// and the identical ownership rule; (3) re-execute
|
|
14
14
|
// this process via the seam `ui/src/bun-runtime.ts` exposes, so any conductor
|
|
15
15
|
// reading the SAME config file re-binds on its next read.
|
|
16
16
|
import { workspaceOwner, writeWorkspaceIdSelection } from '@gaia-ai/core';
|
|
@@ -158,6 +158,22 @@ export interface UiTable<Row extends {
|
|
|
158
158
|
onActivate?(row: Row, ctx: PanelRender): Promise<ActivationResult>;
|
|
159
159
|
/** Shown when `rows` comes back empty. */
|
|
160
160
|
readonly emptyMessage?: string;
|
|
161
|
+
/**
|
|
162
|
+
* GAIA-500 — what `l` copies for the selected row, when this table is the
|
|
163
|
+
* active tab: the LOCAL address, mirroring `s`/`x` (GAIA-475 UX), two
|
|
164
|
+
* distinct keys rather than one that means something different depending on
|
|
165
|
+
* state. Optional: a table with nothing local-shaped to copy leaves `l`
|
|
166
|
+
* unhandled here, and the key falls through inert — never a copy of
|
|
167
|
+
* something the row does not own.
|
|
168
|
+
*/
|
|
169
|
+
copyLocal?(row: Row): string | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* GAIA-500 — what `t` copies for the selected row: the address reached
|
|
172
|
+
* through the TUNNEL, when the row has one shared. `undefined` when nothing
|
|
173
|
+
* is shared — `t` on a `local only` row does nothing rather than copying an
|
|
174
|
+
* empty string or a stale value.
|
|
175
|
+
*/
|
|
176
|
+
copyTunnel?(row: Row): string | undefined;
|
|
161
177
|
}
|
|
162
178
|
export type UiContribution = UiPanel | UiTable | UiScreen;
|
|
163
179
|
/**
|
package/dist/src/types.d.ts
CHANGED
|
@@ -148,8 +148,8 @@ export interface RendererServices {
|
|
|
148
148
|
workspace?: string;
|
|
149
149
|
/**
|
|
150
150
|
* GAIA-472 — the absolute path of the loaded connection config, so the
|
|
151
|
-
* workspace-picker screen can find `workspace_id`'s owning file the
|
|
152
|
-
*
|
|
151
|
+
* workspace-picker screen can find `workspace_id`'s owning file through the
|
|
152
|
+
* shared ownership rule (`@gaia-ai/core`'s `workspaceOwner`).
|
|
153
153
|
*/
|
|
154
154
|
configPath?: string;
|
|
155
155
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaia-ai/addon-gaia-ui",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "GAIA TUI: interactive terminal UI (dashboard, projects, tickets) as a dropsh `tui` renderer plugin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,6 +26,6 @@
|
|
|
26
26
|
"drupal-jsonapi-params": "^3.0.1"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@gaia-ai/core": "^0.13.
|
|
29
|
+
"@gaia-ai/core": "^0.13.1"
|
|
30
30
|
}
|
|
31
31
|
}
|