@gaia-ai/addon-gaia-ui 0.11.5 → 0.12.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.
Files changed (47) hide show
  1. package/dist/src/Entity/conductor/Data/conductors.d.ts +34 -9
  2. package/dist/src/Entity/conductor/Data/conductors.js +65 -18
  3. package/dist/src/Entity/conductor/Data/own-conductor.d.ts +5 -1
  4. package/dist/src/Entity/conductor/Data/own-conductor.js +5 -1
  5. package/dist/src/Entity/conductor/Data/own-rows.d.ts +59 -0
  6. package/dist/src/Entity/conductor/Data/own-rows.js +87 -0
  7. package/dist/src/Entity/conductor/Screen/conductors.d.ts +92 -16
  8. package/dist/src/Entity/conductor/Screen/conductors.js +103 -38
  9. package/dist/src/Entity/conductor/Screen/project-conductors.d.ts +99 -21
  10. package/dist/src/Entity/conductor/Screen/project-conductors.js +122 -51
  11. package/dist/src/Entity/project/Data/project.d.ts +8 -0
  12. package/dist/src/Entity/project/Data/project.js +18 -2
  13. package/dist/src/Entity/run/Data/query.d.ts +3 -1
  14. package/dist/src/Entity/run/Data/query.js +3 -1
  15. package/dist/src/Entity/statistics/Data/statistics.d.ts +99 -146
  16. package/dist/src/Entity/statistics/Data/statistics.js +118 -163
  17. package/dist/src/Entity/statistics/Screen/dashboard.d.ts +24 -40
  18. package/dist/src/Entity/statistics/Screen/dashboard.js +111 -214
  19. package/dist/src/Entity/statistics/render.d.ts +6 -0
  20. package/dist/src/Entity/statistics/render.js +69 -0
  21. package/dist/src/Entity/team/Screen/team-users.js +28 -10
  22. package/dist/src/Entity/team/Screen/teams-screen.d.ts +4 -4
  23. package/dist/src/Entity/team/Screen/teams-screen.js +29 -80
  24. package/dist/src/Entity/ticket/Form/create-data.d.ts +8 -0
  25. package/dist/src/Entity/ticket/Form/create-data.js +11 -4
  26. package/dist/src/Entity/ticket/Form/edit-data.d.ts +2 -0
  27. package/dist/src/Entity/ticket/Form/edit-data.js +6 -4
  28. package/dist/src/Entity/ticket/Screen/create-ticket-form.d.ts +7 -0
  29. package/dist/src/Entity/ticket/Screen/create-ticket-form.js +18 -0
  30. package/dist/src/Entity/ticket/Screen/edit-assignment-form.d.ts +7 -0
  31. package/dist/src/Entity/ticket/Screen/edit-assignment-form.js +21 -1
  32. package/dist/src/Entity/user/Screen/user-overview.d.ts +4 -4
  33. package/dist/src/Entity/user/Screen/user-overview.js +20 -99
  34. package/dist/src/Entity/workspace/Data/workspaces.d.ts +20 -0
  35. package/dist/src/Entity/workspace/Data/workspaces.js +20 -0
  36. package/dist/src/Entity/workspace/Screen/workspace-picker.d.ts +34 -0
  37. package/dist/src/Entity/workspace/Screen/workspace-picker.js +167 -0
  38. package/dist/src/Kernel/chrome.d.ts +10 -0
  39. package/dist/src/Kernel/chrome.js +26 -5
  40. package/dist/src/Kernel/tui-kernel.js +22 -1
  41. package/dist/src/Routing/routes.d.ts +6 -0
  42. package/dist/src/Routing/routes.js +8 -0
  43. package/dist/src/launcher.d.ts +9 -0
  44. package/dist/src/launcher.js +7 -0
  45. package/dist/src/plugin.js +13 -0
  46. package/dist/src/types.d.ts +36 -0
  47. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
1
  import type { DropshClient } from '../../../types.js';
2
+ import { type Page } from '../../collection.js';
2
3
  import { type ListOutcome, type OptionClients } from '../../transport.js';
3
4
  import { type ConductorStatus } from './liveness.js';
4
5
  /** One conductor of the control plane, as this renderer needs it. */
@@ -12,6 +13,8 @@ export interface Conductor {
12
13
  status: string | undefined;
13
14
  /** The conductor's project, `null` when unlinked. */
14
15
  projectId: string | null;
16
+ /** The user who owns this record, `null` when unowned (GAIA-460 D8/D9). */
17
+ ownerUserId: string | null;
15
18
  }
16
19
  /**
17
20
  * The conductor collection through one client, in server order.
@@ -375,19 +378,41 @@ export interface ConductorRowContext {
375
378
  */
376
379
  thisHostKey: string;
377
380
  }
381
+ /** What narrows a conductor read — both filters AND together in one query. */
382
+ export interface ConductorQuery {
383
+ /** The project's own conductors (GAIA-450 D13). */
384
+ projectId?: string | undefined;
385
+ /** The conductors this user OWNS (GAIA-460 D3) — by owner, never by machine. */
386
+ ownerUserId?: string | undefined;
387
+ }
378
388
  /**
379
- * The conductor collection as cockpit rows, in server order.
389
+ * One PAGE of the conductor collection as cockpit rows (GAIA-460 D6).
390
+ *
391
+ * Sideloads `machine` so capacity (GAIA-353 D5/D10) comes from the host budget.
392
+ * The owner filter rides this same query rather than the returned rows (D3):
393
+ * applied to an already-bounded page it would hide GAIA-458's truncation instead
394
+ * of fixing it.
380
395
  *
381
- * Sideloads `machine` so capacity (GAIA-353 D5/D10) comes from the host budget,
382
- * not removed conductor fields. Swallows a read failure into an empty list on
383
- * purpose: the Conductors screen re-reads every 5 s, so one failed poll must
384
- * leave the screen navigable rather than replace it with an error.
396
+ * A FAILED READ PROPAGATES, and that is the change D6 buys rather than an
397
+ * oversight. The read this replaced swallowed a failure into an empty list, so
398
+ * one failed 5 s poll left the screen navigable; on the shared `ListScreen`
399
+ * base a rejected load is the error state every other GAIA list already shows,
400
+ * and one collection may not keep a second spelling of what a failure looks
401
+ * like. The cost is real — a single bad poll now replaces the screen — and it
402
+ * is the convention's cost, not this read's decision to make.
403
+ */
404
+ export declare const listConductorRowPage: (client: DropshClient, ctx: ConductorRowContext, query: ConductorQuery, page?: number) => Promise<Page<ConductorRow>>;
405
+ /**
406
+ * How large the collection under `query` is, or `null` when that is not
407
+ * established (GAIA-460 D4).
385
408
  *
386
- * GAIA-450 D13: an optional `projectId` narrows the read to the project's own
387
- * conductors — the project-scoped Conductors tab's whole filter, applied at
388
- * the query rather than client-side so the read stays one round trip.
409
+ * One row is requested because no row is wanted: the answer is `meta.count`, and
410
+ * asking for a page of them to count them is the walk GAIA-292 removed. `null`
411
+ * covers both a plane that reports no count (R1) and a failed probe — an
412
+ * explanation is never a precondition, so its failure may not disturb the list
413
+ * it was meant to explain.
389
414
  */
390
- export declare const listConductorRows: (client: DropshClient, ctx: ConductorRowContext, projectId?: string) => Promise<ConductorRow[]>;
415
+ export declare const countConductors: (client: DropshClient, query: ConductorQuery) => Promise<number | null>;
391
416
  /**
392
417
  * Project uuid → name, for the Conductors screen's project column.
393
418
  *
@@ -6,6 +6,8 @@
6
6
  import { toUnixSeconds } from '@gaia-ai/core';
7
7
  import { DrupalJsonApiParams } from 'drupal-jsonapi-params';
8
8
  import { humanizeAge } from '../../../lib/format.js';
9
+ import { loadPage, } from '../../collection.js';
10
+ import { LIST_PAGE_SIZE } from '../../cycle.js';
9
11
  import { listOutcome, listPreferringWrite, } from '../../transport.js';
10
12
  import { deriveConductorStatus } from './liveness.js';
11
13
  const str = (v) => typeof v === 'string' && v.length > 0 ? v : undefined;
@@ -19,6 +21,7 @@ const toConductor = (r) => ({
19
21
  machineId: str(r.attr('conductor_id')) ?? str(r.attr('machine_id')),
20
22
  status: str(r.attr('status')),
21
23
  projectId: r.rel('project_id'),
24
+ ownerUserId: r.rel('owner_user_id'),
22
25
  });
23
26
  /**
24
27
  * The conductor collection through one client, in server order.
@@ -377,32 +380,76 @@ const toConductorRow = (raw, ctx, machinesById) => {
377
380
  };
378
381
  };
379
382
  /**
380
- * The conductor collection as cockpit rows, in server order.
383
+ * The query both reads share: the narrowing, the declared order, the sideload.
381
384
  *
382
- * Sideloads `machine` so capacity (GAIA-353 D5/D10) comes from the host budget,
383
- * not removed conductor fields. Swallows a read failure into an empty list on
384
- * purpose: the Conductors screen re-reads every 5 s, so one failed poll must
385
- * leave the screen navigable rather than replace it with an error.
386
- *
387
- * GAIA-450 D13: an optional `projectId` narrows the read to the project's own
388
- * conductors — the project-scoped Conductors tab's whole filter, applied at
389
- * the query rather than client-side so the read stays one round trip.
385
+ * `sort=-last_seen` is not cosmetic (GAIA-460 D7, GAIA-458 AC-2). Paging over a
386
+ * collection with no declared order lets a row repeat on two pages while another
387
+ * is skipped entirely, so the order is a correctness property of the paging
388
+ * before it is a convenience for the reader.
390
389
  */
391
- export const listConductorRows = async (client, ctx, projectId) => {
390
+ const conductorParams = (query) => {
392
391
  const params = new DrupalJsonApiParams().addInclude(['machine']);
393
- if (projectId)
394
- params.addFilter('project_id.id', projectId);
395
- try {
396
- const doc = (await client.get('gaia_conductor/gaia_conductor', params));
392
+ params.addSort('last_seen', 'DESC');
393
+ if (query.projectId)
394
+ params.addFilter('project_id.id', query.projectId);
395
+ if (query.ownerUserId)
396
+ params.addFilter('owner_user_id.id', query.ownerUserId);
397
+ return params;
398
+ };
399
+ /**
400
+ * One PAGE of the conductor collection as cockpit rows (GAIA-460 D6).
401
+ *
402
+ * Sideloads `machine` so capacity (GAIA-353 D5/D10) comes from the host budget.
403
+ * The owner filter rides this same query rather than the returned rows (D3):
404
+ * applied to an already-bounded page it would hide GAIA-458's truncation instead
405
+ * of fixing it.
406
+ *
407
+ * A FAILED READ PROPAGATES, and that is the change D6 buys rather than an
408
+ * oversight. The read this replaced swallowed a failure into an empty list, so
409
+ * one failed 5 s poll left the screen navigable; on the shared `ListScreen`
410
+ * base a rejected load is the error state every other GAIA list already shows,
411
+ * and one collection may not keep a second spelling of what a failure looks
412
+ * like. The cost is real — a single bad poll now replaces the screen — and it
413
+ * is the convention's cost, not this read's decision to make.
414
+ */
415
+ export const listConductorRowPage = async (client, ctx, query, page = 0) => {
416
+ const window = {
417
+ offset: page * LIST_PAGE_SIZE,
418
+ limit: LIST_PAGE_SIZE,
419
+ };
420
+ const params = conductorParams(query);
421
+ params.addPageLimit(window.limit);
422
+ params.addPageOffset(window.offset);
423
+ return loadPage(client, 'gaia_conductor/gaia_conductor', params, (data, included) => {
397
424
  const machinesById = new Map();
398
- for (const inc of doc.included ?? []) {
425
+ for (const inc of included)
399
426
  if (inc.id)
400
427
  machinesById.set(inc.id, inc);
401
- }
402
- return (doc.data ?? []).map((raw) => toConductorRow(raw, ctx, machinesById));
428
+ return data.map((raw) => toConductorRow(raw, ctx, machinesById));
429
+ }, page, window);
430
+ };
431
+ /**
432
+ * How large the collection under `query` is, or `null` when that is not
433
+ * established (GAIA-460 D4).
434
+ *
435
+ * One row is requested because no row is wanted: the answer is `meta.count`, and
436
+ * asking for a page of them to count them is the walk GAIA-292 removed. `null`
437
+ * covers both a plane that reports no count (R1) and a failed probe — an
438
+ * explanation is never a precondition, so its failure may not disturb the list
439
+ * it was meant to explain.
440
+ */
441
+ export const countConductors = async (client, query) => {
442
+ const params = conductorParams(query);
443
+ params.addPageLimit(1);
444
+ try {
445
+ const doc = (await client.get('gaia_conductor/gaia_conductor', params));
446
+ const count = doc?.meta?.count;
447
+ return typeof count === 'number' && Number.isFinite(count) && count >= 0
448
+ ? count
449
+ : null;
403
450
  }
404
451
  catch {
405
- return [];
452
+ return null;
406
453
  }
407
454
  };
408
455
  /**
@@ -21,7 +21,11 @@ export declare function selectOwnConductor(doc: JsonApiCollectionDoc, gaiaDir: s
21
21
  * Read the conductor collection and resolve this checkout's own record.
22
22
  *
23
23
  * One request with the project sideloaded, so the project NAME costs no second
24
- * round trip. The read is owner-gated, so it is already this user's conductors.
24
+ * round trip. The read is NOT owner-gated: GAIA-391 granted `view gaia_conductor` to every
25
+ * `gaia:session` token, so this reads the whole collection. That is harmless
26
+ * here — the match is on `workspace_root`, a path on THIS machine, which no
27
+ * other user's record can hold. GAIA-460 filters the conductor LIST by owner;
28
+ * this lookup needs no filter because its predicate is already stricter.
25
29
  *
26
30
  * A failure yields nothing rather than rejecting: this runs on the boot path,
27
31
  * and an unreachable control plane must leave the cockpit openable — the two
@@ -75,7 +75,11 @@ export function selectOwnConductor(doc, gaiaDir) {
75
75
  * Read the conductor collection and resolve this checkout's own record.
76
76
  *
77
77
  * One request with the project sideloaded, so the project NAME costs no second
78
- * round trip. The read is owner-gated, so it is already this user's conductors.
78
+ * round trip. The read is NOT owner-gated: GAIA-391 granted `view gaia_conductor` to every
79
+ * `gaia:session` token, so this reads the whole collection. That is harmless
80
+ * here — the match is on `workspace_root`, a path on THIS machine, which no
81
+ * other user's record can hold. GAIA-460 filters the conductor LIST by owner;
82
+ * this lookup needs no filter because its predicate is already stricter.
79
83
  *
80
84
  * A failure yields nothing rather than rejecting: this runs on the boot path,
81
85
  * and an unreachable control plane must leave the cockpit openable — the two
@@ -0,0 +1,59 @@
1
+ import type { ListFilterBadge } from '../../../Component/list-chrome/index.js';
2
+ import type { DropshClient } from '../../../types.js';
3
+ /** Who the operator is, or why the screen could not establish it. */
4
+ export type OwnerOutcome = {
5
+ kind: 'resolved';
6
+ id: string;
7
+ label: string;
8
+ } | {
9
+ kind: 'unavailable';
10
+ reason: string;
11
+ };
12
+ /** The read this identity needs — `resolveWhoCandidate`'s shape, injected. */
13
+ export type OwnerResolver = (client: DropshClient, name: string) => Promise<{
14
+ rows: {
15
+ id: string;
16
+ label: string;
17
+ }[];
18
+ error?: string;
19
+ }>;
20
+ /**
21
+ * The operator's user uuid, resolved at most once per screen lifetime.
22
+ *
23
+ * WHAT IS CACHED, AND WHAT IS NOT. A success is cached, and so is a definitive
24
+ * miss — a name the collection does not hold will not start holding it inside one
25
+ * session, and re-asking on every refresh would spend a request per poll to learn
26
+ * the same "no". A READ ERROR is cached by neither: it says nothing about the
27
+ * collection, only that this attempt failed, so the next load tries again
28
+ * (GAIA-287 D5).
29
+ */
30
+ export declare class OwnerIdentity {
31
+ private readonly resolveOwner;
32
+ private cached;
33
+ constructor(resolveOwner: OwnerResolver);
34
+ resolve(client: DropshClient, currentUser: string | undefined): Promise<OwnerOutcome>;
35
+ /** A new project or session means a new context; forget the answer. */
36
+ reset(): void;
37
+ }
38
+ /**
39
+ * The shipped default (GAIA-460 D1) — the list opens showing MINE.
40
+ *
41
+ * A product decision, and deliberately not an inheritance from the access layer:
42
+ * GAIA-391 granted `view gaia_conductor` to every session token on purpose, and
43
+ * this default restores what operators had before it without touching that grant.
44
+ */
45
+ export declare const DEFAULT_CONDUCTOR_MINE = true;
46
+ /** The filter strip both conductor screens show, so neither can word it its own way. */
47
+ export declare const mineBadges: (mine: boolean) => ListFilterBadge[];
48
+ /**
49
+ * `5 hidden by mine — press m`, or '' when there is nothing to say (AC-2).
50
+ *
51
+ * Three cases collapse to the empty string and each for its own reason. `null` is
52
+ * a total nobody established — a plane that reports no `meta.count` (R1) or a
53
+ * failed probe — and a number nobody measured must not be printed. `0` is a
54
+ * measured nothing: an operator sent to press `m` for rows that do not exist
55
+ * learns only that the screen is guessing. Anything else is a fact, and it names
56
+ * the key that releases it, because a count without a remedy is just a smaller
57
+ * mystery.
58
+ */
59
+ export declare const hiddenNotice: (hidden: number | null) => string;
@@ -0,0 +1,87 @@
1
+ // GAIA-460 — "which conductors are MINE?", resolved once per screen.
2
+ //
3
+ // The Conductors list opens FILTERED (D1), so the operator's uuid must be known
4
+ // before the first read rather than on the first keypress the way GAIA-287's
5
+ // ticket list resolves it. That is the whole reason this is a small object with
6
+ // a cache instead of a function: the resolution happens on the load path, may
7
+ // fail there, and must not be retried on every 5 s refresh once it has been
8
+ // definitively answered.
9
+ //
10
+ // It takes the RESOLVER as an argument rather than importing it.
11
+ // `tests/boundary.test.ts` G2 forbids a `Data/` module reaching another slice,
12
+ // and `resolveWhoCandidate` lives in the user slice; the screens inject it, as
13
+ // they already inject `exists` and `thisHostKey` into `ConductorRowContext`.
14
+ const messageOf = (caught) => caught instanceof Error ? caught.message : String(caught);
15
+ /**
16
+ * The operator's user uuid, resolved at most once per screen lifetime.
17
+ *
18
+ * WHAT IS CACHED, AND WHAT IS NOT. A success is cached, and so is a definitive
19
+ * miss — a name the collection does not hold will not start holding it inside one
20
+ * session, and re-asking on every refresh would spend a request per poll to learn
21
+ * the same "no". A READ ERROR is cached by neither: it says nothing about the
22
+ * collection, only that this attempt failed, so the next load tries again
23
+ * (GAIA-287 D5).
24
+ */
25
+ export class OwnerIdentity {
26
+ resolveOwner;
27
+ cached = null;
28
+ constructor(resolveOwner) {
29
+ this.resolveOwner = resolveOwner;
30
+ }
31
+ async resolve(client, currentUser) {
32
+ if (this.cached)
33
+ return this.cached;
34
+ if (currentUser === undefined) {
35
+ // Not cached: the launcher supplies this, and a screen re-entered under a
36
+ // different context may well have one.
37
+ return { kind: 'unavailable', reason: 'mine: no current user' };
38
+ }
39
+ let outcome;
40
+ try {
41
+ outcome = await this.resolveOwner(client, currentUser);
42
+ }
43
+ catch (caught) {
44
+ return { kind: 'unavailable', reason: `mine: ${messageOf(caught)}` };
45
+ }
46
+ if (outcome.error !== undefined) {
47
+ return { kind: 'unavailable', reason: `mine: ${outcome.error}` };
48
+ }
49
+ const row = outcome.rows[0];
50
+ this.cached =
51
+ row === undefined
52
+ ? { kind: 'unavailable', reason: `mine: '${currentUser}' not found` }
53
+ : { kind: 'resolved', id: row.id, label: row.label };
54
+ return this.cached;
55
+ }
56
+ /** A new project or session means a new context; forget the answer. */
57
+ reset() {
58
+ this.cached = null;
59
+ }
60
+ }
61
+ /**
62
+ * The shipped default (GAIA-460 D1) — the list opens showing MINE.
63
+ *
64
+ * A product decision, and deliberately not an inheritance from the access layer:
65
+ * GAIA-391 granted `view gaia_conductor` to every session token on purpose, and
66
+ * this default restores what operators had before it without touching that grant.
67
+ */
68
+ export const DEFAULT_CONDUCTOR_MINE = true;
69
+ /** The filter strip both conductor screens show, so neither can word it its own way. */
70
+ export const mineBadges = (mine) => [
71
+ mine
72
+ ? { id: 'mine', label: 'm mine', value: 'mine' }
73
+ : { id: 'mine', label: 'm mine' },
74
+ { id: 'clear', label: 'c clear' },
75
+ ];
76
+ /**
77
+ * `5 hidden by mine — press m`, or '' when there is nothing to say (AC-2).
78
+ *
79
+ * Three cases collapse to the empty string and each for its own reason. `null` is
80
+ * a total nobody established — a plane that reports no `meta.count` (R1) or a
81
+ * failed probe — and a number nobody measured must not be printed. `0` is a
82
+ * measured nothing: an operator sent to press `m` for rows that do not exist
83
+ * learns only that the screen is guessing. Anything else is a fact, and it names
84
+ * the key that releases it, because a count without a remedy is just a smaller
85
+ * mystery.
86
+ */
87
+ export const hiddenNotice = (hidden) => hidden === null || hidden <= 0 ? '' : `${hidden} hidden by mine — press m`;
@@ -1,21 +1,19 @@
1
1
  import type { VNode } from '@opentui/core';
2
- import { AsyncScreen } from '../../../Screen/async-screen.js';
2
+ import type { ListFilterBadge } from '../../../Component/list-chrome/index.js';
3
3
  import type { FullScreenChrome } from '../../../Screen/full-screen.js';
4
+ import { ListScreen } from '../../../Screen/list-screen.js';
4
5
  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
- }
6
+ import type { Page } from '../../collection.js';
7
+ import { type ConductorListContext, type ConductorRow } from '../Data/conductors.js';
11
8
  /**
12
- * Conductors list — a normal {@link AsyncScreen}.
9
+ * Conductors list — an ordinary {@link ListScreen}, paged and filtered to the
10
+ * operator's own conductors by default (GAIA-460 D1).
13
11
  *
14
12
  * Refresh is the shared mechanism (GAIA-376 `ui-refresh` file + `R`), not a
15
13
  * screen-private timer. Enter loads; navigation away cancels in-flight reads
16
14
  * via the async lifecycle.
17
15
  */
18
- export declare class ConductorsScreen extends AsyncScreen<ConductorsData> {
16
+ export declare class ConductorsScreen extends ListScreen<ConductorRow, ConductorListContext> {
19
17
  static readonly route: {
20
18
  id: string;
21
19
  path: string;
@@ -28,7 +26,68 @@ export declare class ConductorsScreen extends AsyncScreen<ConductorsData> {
28
26
  };
29
27
  readonly id: string;
30
28
  readonly frameId = "conductors";
31
- private selectedIndex;
29
+ protected readonly table: {
30
+ readonly noun: "conductor";
31
+ readonly emptyMessage: "No conductors on this control plane.";
32
+ readonly columns: readonly [{
33
+ readonly key: "name";
34
+ readonly label: "NAME";
35
+ readonly width: 0;
36
+ readonly min: 20;
37
+ readonly grow: 2;
38
+ readonly fields: readonly ["label", "machine_id"];
39
+ readonly get: (row: ConductorRow) => string;
40
+ }, {
41
+ readonly key: "machine";
42
+ readonly label: "MACHINE";
43
+ readonly width: 20;
44
+ readonly fields: readonly ["machine_id"];
45
+ readonly get: (row: ConductorRow) => string;
46
+ }, {
47
+ readonly key: "project";
48
+ readonly label: "PROJECT";
49
+ readonly width: 14;
50
+ readonly grow: 1;
51
+ readonly fields: readonly ["project_id"];
52
+ readonly get: (row: ConductorRow, ctx: ConductorListContext) => string;
53
+ }, {
54
+ readonly key: "status";
55
+ readonly label: "STATUS";
56
+ readonly width: 9;
57
+ readonly fields: readonly ["status", "lease_expires_at", "last_seen"];
58
+ readonly get: (row: ConductorRow) => import("../Data/liveness.js").ConductorStatus;
59
+ }, {
60
+ readonly key: "age";
61
+ readonly label: "AGE";
62
+ readonly width: 8;
63
+ readonly fields: readonly ["last_seen"];
64
+ readonly get: (row: ConductorRow, ctx: ConductorListContext) => string;
65
+ }, {
66
+ readonly key: "workspace";
67
+ readonly label: "WORKSPACE";
68
+ readonly width: 18;
69
+ readonly fields: readonly ["workspace_root"];
70
+ readonly get: (row: ConductorRow) => string;
71
+ }, {
72
+ readonly key: "pid";
73
+ readonly label: "PID";
74
+ readonly width: 7;
75
+ readonly fields: readonly ["last_pid"];
76
+ readonly get: (row: ConductorRow) => string;
77
+ }, {
78
+ readonly key: "marks";
79
+ readonly label: "MARKS";
80
+ readonly width: 11;
81
+ readonly fields: readonly ["workspace_root"];
82
+ readonly get: (row: ConductorRow, ctx: ConductorListContext) => string;
83
+ }, {
84
+ readonly key: "load";
85
+ readonly label: "LOAD";
86
+ readonly width: 5;
87
+ readonly fields: readonly ["current_load", "max_parallel"];
88
+ readonly get: (row: ConductorRow) => string;
89
+ }];
90
+ };
32
91
  private logFor;
33
92
  private readonly busyIds;
34
93
  /**
@@ -39,16 +98,34 @@ export declare class ConductorsScreen extends AsyncScreen<ConductorsData> {
39
98
  * cannot act on is marked BEFORE a key is pressed, not only after.
40
99
  */
41
100
  private status;
101
+ /**
102
+ * GAIA-460 review — separate from {@link status}: the identity reason is
103
+ * how `mine` failed to resolve, not an action's outcome. Sharing one field
104
+ * let `loadRows()` blank a start/stop result on every refresh and let a
105
+ * standing identity reason permanently block {@link viewOnlyNotice}; kept
106
+ * apart, each notice source has its own lifecycle.
107
+ */
108
+ private identityReason;
109
+ private mine;
110
+ private readonly identity;
111
+ private ownerId;
112
+ private projectNames;
113
+ private hidden;
42
114
  topTab(): {
43
115
  readonly level: "root";
44
116
  readonly tab: "conductors";
45
117
  };
46
- reload(context: ScreenContext): void;
47
- get rows(): readonly ConductorRow[];
48
- protected load(context: ScreenContext): Promise<ConductorsData>;
118
+ protected tableContext(context: ScreenContext): ConductorListContext;
119
+ /**
120
+ * GAIA-460 D5 — the identity is resolved BEFORE the first read, because the
121
+ * filter is on by default. Every way of failing to establish it loads the
122
+ * FULL list and states the reason: a cockpit that cannot prove who you are
123
+ * shows you everything, never an empty list it cannot explain.
124
+ */
125
+ protected loadRows(context: ScreenContext, page: number): Promise<Page<ConductorRow>>;
126
+ protected badges(): readonly ListFilterBadge[];
49
127
  protected renderLoading(): string[];
50
- protected renderReady(data: ConductorsData, context: ScreenContext): VNode;
51
- protected didLoad(data: ConductorsData): void;
128
+ protected renderReady(value: Page<ConductorRow>, context: ScreenContext): VNode;
52
129
  protected chrome(_context: ScreenContext): FullScreenChrome;
53
130
  /**
54
131
  * GAIA-450 review B9 — `s`/`x` are advertised only when the SELECTED row can
@@ -92,4 +169,3 @@ export declare class ConductorsScreen extends AsyncScreen<ConductorsData> {
92
169
  */
93
170
  private act;
94
171
  }
95
- export {};