@gaia-ai/addon-gaia-ui 0.16.0 → 0.16.2

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 (220) hide show
  1. package/dist/src/Component/badge/index.d.ts +11 -0
  2. package/dist/src/Component/badge/index.js +10 -0
  3. package/dist/src/Component/bar-chart/index.d.ts +16 -0
  4. package/dist/src/Component/bar-chart/index.js +63 -0
  5. package/dist/src/Component/card/index.d.ts +16 -0
  6. package/dist/src/Component/card/index.js +39 -0
  7. package/dist/src/Component/divider/index.d.ts +7 -0
  8. package/dist/src/Component/divider/index.js +13 -0
  9. package/dist/src/Component/heat-map/index.d.ts +21 -0
  10. package/dist/src/Component/heat-map/index.js +86 -0
  11. package/dist/src/Component/key-value/index.d.ts +18 -0
  12. package/dist/src/Component/key-value/index.js +30 -0
  13. package/dist/src/Component/markdown/index.d.ts +35 -5
  14. package/dist/src/Component/markdown/index.js +72 -16
  15. package/dist/src/Entity/team/Data/workload.d.ts +3 -1
  16. package/dist/src/Entity/team/Data/workload.js +6 -4
  17. package/dist/src/Entity/team/Screen/team-work.d.ts +12 -1
  18. package/dist/src/Entity/team/Screen/team-work.js +47 -2
  19. package/dist/src/Entity/ticket/Component/demo-statistics.d.ts +14 -0
  20. package/dist/src/Entity/ticket/Component/demo-statistics.js +88 -0
  21. package/dist/src/Entity/ticket/Component/react-statistics.d.ts +14 -22
  22. package/dist/src/Entity/ticket/Component/react-statistics.js +21 -42
  23. package/dist/src/Entity/ticket/Component/ticket-content/index.d.ts +28 -3
  24. package/dist/src/Entity/ticket/Component/ticket-content/index.js +331 -157
  25. package/dist/src/Entity/ticket/Data/parents.js +1 -1
  26. package/dist/src/Entity/ticket/Data/query.d.ts +2 -2
  27. package/dist/src/Entity/ticket/Data/query.js +13 -9
  28. package/dist/src/Entity/ticket/Data/store.js +1 -1
  29. package/dist/src/Entity/ticket/Data/tickets.d.ts +1 -1
  30. package/dist/src/Entity/ticket/Data/tickets.js +1 -1
  31. package/dist/src/Entity/ticket/Form/edit.d.ts +13 -0
  32. package/dist/src/Entity/ticket/Form/edit.js +17 -7
  33. package/dist/src/Entity/ticket/Screen/conductor-tickets.d.ts +7 -1
  34. package/dist/src/Entity/ticket/Screen/conductor-tickets.js +30 -6
  35. package/dist/src/Entity/ticket/Screen/edit-assignment-form.d.ts +3 -1
  36. package/dist/src/Entity/ticket/Screen/edit-assignment-form.js +21 -6
  37. package/dist/src/Entity/ticket/Screen/my-work.d.ts +8 -1
  38. package/dist/src/Entity/ticket/Screen/my-work.js +37 -4
  39. package/dist/src/Entity/ticket/Screen/ticket-comments.d.ts +6 -12
  40. package/dist/src/Entity/ticket/Screen/ticket-comments.js +11 -16
  41. package/dist/src/Entity/ticket/Screen/ticket-content-screen.d.ts +25 -44
  42. package/dist/src/Entity/ticket/Screen/ticket-content-screen.js +79 -87
  43. package/dist/src/Entity/ticket/Screen/ticket-detail.d.ts +6 -11
  44. package/dist/src/Entity/ticket/Screen/ticket-detail.js +14 -19
  45. package/dist/src/Entity/ticket/Screen/ticket-filter-bar.js +2 -9
  46. package/dist/src/Entity/ticket/Screen/ticket-planning.d.ts +6 -12
  47. package/dist/src/Entity/ticket/Screen/ticket-planning.js +11 -16
  48. package/dist/src/Entity/ticket/Screen/ticket-react-markdown.d.ts +9 -7
  49. package/dist/src/Entity/ticket/Screen/ticket-react-markdown.js +36 -13
  50. package/dist/src/Entity/ticket/Screen/ticket-react.d.ts +21 -8
  51. package/dist/src/Entity/ticket/Screen/ticket-react.js +109 -34
  52. package/dist/src/Entity/ticket/Screen/ticket-search.d.ts +44 -0
  53. package/dist/src/Entity/ticket/Screen/ticket-search.js +97 -0
  54. package/dist/src/Entity/ticket/Screen/tickets.d.ts +9 -7
  55. package/dist/src/Entity/ticket/Screen/tickets.js +25 -85
  56. package/dist/src/Entity/ticket/Screen/user-tickets.d.ts +7 -0
  57. package/dist/src/Entity/ticket/Screen/user-tickets.js +30 -2
  58. package/dist/src/Kernel/renderer.d.ts +1 -3
  59. package/dist/src/Kernel/renderer.js +2 -5
  60. package/dist/src/Kernel/tui-kernel.js +7 -16
  61. package/dist/src/Screen/async-screen.d.ts +13 -0
  62. package/dist/src/Screen/async-screen.js +34 -5
  63. package/dist/src/Screen/list-screen.d.ts +11 -2
  64. package/dist/src/Screen/list-screen.js +20 -2
  65. package/dist/src/Screen/screen.d.ts +6 -2
  66. package/dist/src/Statistics/comparison-chart.d.ts +4 -2
  67. package/dist/src/Statistics/comparison-chart.js +14 -6
  68. package/dist/src/Statistics/figures.d.ts +17 -3
  69. package/dist/src/Statistics/figures.js +104 -41
  70. package/dist/src/Statistics/presentation.d.ts +3 -5
  71. package/dist/src/Statistics/presentation.js +3 -5
  72. package/dist/src/Statistics/screen.d.ts +16 -5
  73. package/dist/src/Statistics/screen.js +59 -26
  74. package/dist/src/lib/theme.d.ts +0 -56
  75. package/dist/src/lib/theme.js +0 -42
  76. package/dist/src/lib/vnode.d.ts +29 -0
  77. package/dist/src/lib/vnode.js +69 -0
  78. package/dist/src/route.js +37 -0
  79. package/dist/src/types.d.ts +5 -1
  80. package/dist/src/ui.d.ts +0 -1
  81. package/dist/src/ui.js +0 -1
  82. package/package.json +2 -2
  83. package/dist/src/Agent/Component/agent-list/index.d.ts +0 -6
  84. package/dist/src/Agent/Component/agent-list/index.js +0 -14
  85. package/dist/src/Component/agent-list/index.d.ts +0 -6
  86. package/dist/src/Component/agent-list/index.js +0 -14
  87. package/dist/src/Component/comment-item/index.d.ts +0 -21
  88. package/dist/src/Component/comment-item/index.js +0 -39
  89. package/dist/src/Component/create-form/index.d.ts +0 -3
  90. package/dist/src/Component/create-form/index.js +0 -26
  91. package/dist/src/Component/dashboard-list/index.d.ts +0 -8
  92. package/dist/src/Component/dashboard-list/index.js +0 -7
  93. package/dist/src/Component/project-picker/index.d.ts +0 -15
  94. package/dist/src/Component/project-picker/index.js +0 -42
  95. package/dist/src/Component/run-table/index.d.ts +0 -10
  96. package/dist/src/Component/run-table/index.js +0 -20
  97. package/dist/src/Component/tab-bar/index.d.ts +0 -20
  98. package/dist/src/Component/tab-bar/index.js +0 -42
  99. package/dist/src/Component/ticket-detail/index.d.ts +0 -38
  100. package/dist/src/Component/ticket-detail/index.js +0 -193
  101. package/dist/src/Component/ticket-table/index.d.ts +0 -5
  102. package/dist/src/Component/ticket-table/index.js +0 -14
  103. package/dist/src/Component/ticket-teaser/index.d.ts +0 -7
  104. package/dist/src/Component/ticket-teaser/index.js +0 -76
  105. package/dist/src/Deriver/liveness.d.ts +0 -12
  106. package/dist/src/Deriver/liveness.js +0 -34
  107. package/dist/src/Deriver/run-status.d.ts +0 -12
  108. package/dist/src/Deriver/run-status.js +0 -36
  109. package/dist/src/Entity/conductor/Component/conductor-list/index.d.ts +0 -25
  110. package/dist/src/Entity/conductor/Component/conductor-list/index.js +0 -60
  111. package/dist/src/Entity/project/Component/dashboard-list/index.d.ts +0 -8
  112. package/dist/src/Entity/project/Component/dashboard-list/index.js +0 -7
  113. package/dist/src/Entity/project/Component/project-list/index.d.ts +0 -8
  114. package/dist/src/Entity/project/Component/project-list/index.js +0 -7
  115. package/dist/src/Entity/project/Component/project-picker/index.d.ts +0 -15
  116. package/dist/src/Entity/project/Component/project-picker/index.js +0 -42
  117. package/dist/src/Entity/project/Screen/project-work.d.ts +0 -33
  118. package/dist/src/Entity/project/Screen/project-work.js +0 -114
  119. package/dist/src/Entity/run/Component/run-table/index.d.ts +0 -8
  120. package/dist/src/Entity/run/Component/run-table/index.js +0 -11
  121. package/dist/src/Entity/statistics/Data/statistics.d.ts +0 -147
  122. package/dist/src/Entity/statistics/Data/statistics.js +0 -235
  123. package/dist/src/Entity/statistics/Screen/dashboard.d.ts +0 -70
  124. package/dist/src/Entity/statistics/Screen/dashboard.js +0 -216
  125. package/dist/src/Entity/statistics/render.d.ts +0 -6
  126. package/dist/src/Entity/statistics/render.js +0 -71
  127. package/dist/src/Entity/ticket/Component/tab-bar/index.d.ts +0 -20
  128. package/dist/src/Entity/ticket/Component/tab-bar/index.js +0 -71
  129. package/dist/src/Entity/ticket/Component/ticket-table/index.d.ts +0 -7
  130. package/dist/src/Entity/ticket/Component/ticket-table/index.js +0 -10
  131. package/dist/src/Entity/user/Screen/user-overview.d.ts +0 -33
  132. package/dist/src/Entity/user/Screen/user-overview.js +0 -80
  133. package/dist/src/Extension/contract.d.ts +0 -204
  134. package/dist/src/Extension/contract.js +0 -1
  135. package/dist/src/Kernel/react-body.d.ts +0 -9
  136. package/dist/src/Kernel/react-body.js +0 -42
  137. package/dist/src/Screen/async-react-screen.d.ts +0 -45
  138. package/dist/src/Screen/async-react-screen.js +0 -120
  139. package/dist/src/Screen/dashboard.d.ts +0 -1
  140. package/dist/src/Screen/dashboard.js +0 -1
  141. package/dist/src/Screen/project.d.ts +0 -1
  142. package/dist/src/Screen/project.js +0 -1
  143. package/dist/src/Screen/projects.d.ts +0 -1
  144. package/dist/src/Screen/projects.js +0 -1
  145. package/dist/src/Screen/react-screen.d.ts +0 -14
  146. package/dist/src/Screen/react-screen.js +0 -34
  147. package/dist/src/Screen/ticket.d.ts +0 -1
  148. package/dist/src/Screen/ticket.js +0 -1
  149. package/dist/src/Service/agent.d.ts +0 -17
  150. package/dist/src/Service/agent.js +0 -28
  151. package/dist/src/Service/create-form-data.d.ts +0 -10
  152. package/dist/src/Service/create-form-data.js +0 -52
  153. package/dist/src/Service/create-form.d.ts +0 -52
  154. package/dist/src/Service/create-form.js +0 -98
  155. package/dist/src/Service/project-data.d.ts +0 -48
  156. package/dist/src/Service/project-data.js +0 -92
  157. package/dist/src/Service/projects.d.ts +0 -21
  158. package/dist/src/Service/projects.js +0 -80
  159. package/dist/src/Service/route.d.ts +0 -11
  160. package/dist/src/Service/route.js +0 -215
  161. package/dist/src/Service/run-data.d.ts +0 -35
  162. package/dist/src/Service/run-data.js +0 -62
  163. package/dist/src/Service/ticket-data.d.ts +0 -6
  164. package/dist/src/Service/ticket-data.js +0 -7
  165. package/dist/src/Tui/controller.d.ts +0 -13
  166. package/dist/src/Tui/controller.js +0 -1794
  167. package/dist/src/Vendor/termcn/hooks/use-motion.d.ts +0 -5
  168. package/dist/src/Vendor/termcn/hooks/use-motion.js +0 -7
  169. package/dist/src/Vendor/termcn/hooks/use-theme.d.ts +0 -5
  170. package/dist/src/Vendor/termcn/hooks/use-theme.js +0 -10
  171. package/dist/src/Vendor/termcn/hooks/use-unicode.d.ts +0 -5
  172. package/dist/src/Vendor/termcn/hooks/use-unicode.js +0 -34
  173. package/dist/src/Vendor/termcn/lib/dither-chart-utils.d.ts +0 -130
  174. package/dist/src/Vendor/termcn/lib/dither-chart-utils.js +0 -357
  175. package/dist/src/Vendor/termcn/lib/dither-line-chart-utils.d.ts +0 -2
  176. package/dist/src/Vendor/termcn/lib/dither-line-chart-utils.js +0 -72
  177. package/dist/src/Vendor/termcn/lib/dither-radar-chart-utils.d.ts +0 -2
  178. package/dist/src/Vendor/termcn/lib/dither-radar-chart-utils.js +0 -113
  179. package/dist/src/Vendor/termcn/themes/default.d.ts +0 -1
  180. package/dist/src/Vendor/termcn/themes/default.js +0 -3
  181. package/dist/src/Vendor/termcn/ui/badge.d.ts +0 -13
  182. package/dist/src/Vendor/termcn/ui/badge.js +0 -33
  183. package/dist/src/Vendor/termcn/ui/bar-chart.d.ts +0 -16
  184. package/dist/src/Vendor/termcn/ui/bar-chart.js +0 -61
  185. package/dist/src/Vendor/termcn/ui/card.d.ts +0 -18
  186. package/dist/src/Vendor/termcn/ui/card.js +0 -7
  187. package/dist/src/Vendor/termcn/ui/dither-chart.d.ts +0 -118
  188. package/dist/src/Vendor/termcn/ui/dither-chart.js +0 -418
  189. package/dist/src/Vendor/termcn/ui/dither-line-chart.d.ts +0 -10
  190. package/dist/src/Vendor/termcn/ui/dither-line-chart.js +0 -11
  191. package/dist/src/Vendor/termcn/ui/dither-radar-chart.d.ts +0 -21
  192. package/dist/src/Vendor/termcn/ui/dither-radar-chart.js +0 -11
  193. package/dist/src/Vendor/termcn/ui/divider.d.ts +0 -14
  194. package/dist/src/Vendor/termcn/ui/divider.js +0 -25
  195. package/dist/src/Vendor/termcn/ui/gauge.d.ts +0 -12
  196. package/dist/src/Vendor/termcn/ui/gauge.js +0 -48
  197. package/dist/src/Vendor/termcn/ui/heat-map.d.ts +0 -13
  198. package/dist/src/Vendor/termcn/ui/heat-map.js +0 -59
  199. package/dist/src/Vendor/termcn/ui/key-value.d.ts +0 -16
  200. package/dist/src/Vendor/termcn/ui/key-value.js +0 -24
  201. package/dist/src/Vendor/termcn/ui/line-chart.d.ts +0 -15
  202. package/dist/src/Vendor/termcn/ui/line-chart.js +0 -102
  203. package/dist/src/Vendor/termcn/ui/markdown.d.ts +0 -24
  204. package/dist/src/Vendor/termcn/ui/markdown.js +0 -39
  205. package/dist/src/Vendor/termcn/ui/progress-bar.d.ts +0 -13
  206. package/dist/src/Vendor/termcn/ui/progress-bar.js +0 -13
  207. package/dist/src/Vendor/termcn/ui/sparkline.d.ts +0 -8
  208. package/dist/src/Vendor/termcn/ui/sparkline.js +0 -30
  209. package/dist/src/Vendor/termcn/ui/types.d.ts +0 -82
  210. package/dist/src/Vendor/termcn/ui/types.js +0 -1
  211. package/dist/src/lib/ansi.d.ts +0 -34
  212. package/dist/src/lib/ansi.js +0 -97
  213. package/dist/src/lib/hit-test.d.ts +0 -138
  214. package/dist/src/lib/hit-test.js +0 -138
  215. package/dist/src/lib/mouse.d.ts +0 -35
  216. package/dist/src/lib/mouse.js +0 -89
  217. package/dist/src/lib/table.d.ts +0 -37
  218. package/dist/src/lib/table.js +0 -20
  219. package/dist/src/lib/ticket-query.d.ts +0 -21
  220. package/dist/src/lib/ticket-query.js +0 -72
@@ -1,14 +1,19 @@
1
- import type { ListFilterBadge } from '../Component/list-chrome/index.js';
1
+ import { type VNode } from '@opentui/core';
2
+ import { type ListFilterBadge } from '../Component/list-chrome/index.js';
2
3
  import type { SectionRow } from '../Component/section-list/index.js';
3
- import { AsyncReactScreen } from '../Screen/async-react-screen.js';
4
+ import type { FullScreenChrome } from '../Screen/async-screen.js';
5
+ import { AsyncScreen } from '../Screen/async-screen.js';
4
6
  import type { ScreenContext } from '../Screen/screen.js';
5
7
  import type { GroupStatisticsResult, StatisticsFilters } from './client.js';
6
- export declare abstract class StatisticsScreen<T> extends AsyncReactScreen<T> {
8
+ export declare abstract class StatisticsScreen<T> extends AsyncScreen<T> {
7
9
  readonly bodyFrame = false;
10
+ /** The figure grid's laid-out width, as the layout last reported it. */
11
+ private gridWidth;
8
12
  private period;
9
13
  private activeGroupId;
10
14
  protected abstract statistics(value: T): GroupStatisticsResult;
11
15
  protected abstract readyRows(value: T, context: ScreenContext): SectionRow[];
16
+ protected chrome(_context: ScreenContext): FullScreenChrome;
12
17
  private groups;
13
18
  private overviewGroup;
14
19
  private tabbedGroups;
@@ -25,14 +30,20 @@ export declare abstract class StatisticsScreen<T> extends AsyncReactScreen<T> {
25
30
  * One boxed filter chip cycling the three fixed period+granularity presets
26
31
  * (GAIA-548 scope change 7, replacing the former two independent
27
32
  * period/granularity chips of addendum 4). Rendered via
28
- * {@link ChipBadgeStrip} inside `renderReady`, below Overview — see the
33
+ * `filterBadgeStrip` inside `renderReady`, below Overview — see the
29
34
  * override of {@link badges} just below for why.
30
35
  */
31
36
  private statisticsBadges;
32
37
  protected badges(): readonly ListFilterBadge[];
33
- protected renderReady(value: T, context: ScreenContext): import("react").ReactNode;
38
+ /**
39
+ * Both grids share one measured width. A report of a new width is drawn on
40
+ * the next turn, never from inside the layout pass that reported it.
41
+ */
42
+ private figures;
43
+ protected renderReady(value: T, context: ScreenContext): VNode;
34
44
  protected readyLines(value: T, context: ScreenContext): string[];
35
45
  handleInput(key: string, context: ScreenContext): boolean;
46
+ renderNonInteractive(context: ScreenContext): string[];
36
47
  leave(): void;
37
48
  reset(): void;
38
49
  }
@@ -1,27 +1,16 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
1
+ import { BoxRenderable, h } from '@opentui/core';
2
+ import { filterBadgeStrip, } from '../Component/list-chrome/index.js';
2
3
  import { tabPartsFor } from '../Component/top-tabs/index.js';
3
- import { BORDER, DEFAULT_BACKGROUND, FOREGROUND } from '../lib/vnode.js';
4
- import { AsyncReactScreen } from '../Screen/async-react-screen.js';
4
+ import { FOREGROUND, paragraphNode, plainRun } from '../lib/vnode.js';
5
+ import { AsyncScreen } from '../Screen/async-screen.js';
5
6
  import { StatisticsFigures } from './figures.js';
6
7
  import { groupedFigureLines } from './presentation.js';
7
- /**
8
- * The same rounded-chip visual `filterBadgeStrip` (Component/list-chrome)
9
- * renders, expressed in JSX instead of a `h()`-built VNode. `AsyncReactScreen`
10
- * only exposes ONE mount point for React content (`react-screen-host`) and
11
- * places its own `filterBadgeStrip` output above the WHOLE of it — always
12
- * above Overview too, which GAIA-548 addendum 6 forbids. `badges()` is
13
- * therefore overridden to return `[]` (suppressing that framework-level
14
- * strip) and this component renders the same chip data — from the same
15
- * {@link StatisticsScreen.statisticsBadges} source, no cycling logic
16
- * duplicated — directly inside `renderReady`, below Overview.
17
- */
18
- function ChipBadgeStrip({ badges }) {
19
- if (badges.length === 0)
20
- return null;
21
- return (_jsx("box", { flexDirection: "row", flexWrap: "wrap", gap: 1, flexShrink: 0, children: badges.map((badge) => (_jsx("box", { borderStyle: "rounded", borderColor: BORDER, backgroundColor: DEFAULT_BACKGROUND, paddingLeft: 2, paddingRight: 2, flexShrink: 0, children: _jsx("text", { fg: FOREGROUND, children: badge.label }) }, badge.id))) }));
22
- }
23
- export class StatisticsScreen extends AsyncReactScreen {
8
+ /** Grid width before the first layout has measured one — a pipe's width. */
9
+ const DEFAULT_COLUMNS = 80;
10
+ export class StatisticsScreen extends AsyncScreen {
24
11
  bodyFrame = false;
12
+ /** The figure grid's laid-out width, as the layout last reported it. */
13
+ gridWidth;
25
14
  period;
26
15
  // GAIA-548 addendum 5: each figure group (Overview, Breakdown, Tickets,
27
16
  // Runs, Tokens) is its own navigable sub-tab within the Stats screen,
@@ -30,6 +19,9 @@ export class StatisticsScreen extends AsyncReactScreen {
30
19
  // already omits empty groups, so the active tab tracks a group id rather
31
20
  // than a fixed index.
32
21
  activeGroupId;
22
+ chrome(_context) {
23
+ return {};
24
+ }
33
25
  groups(value) {
34
26
  const result = this.statistics(value);
35
27
  return result.kind === 'ok' ? result.groups : [];
@@ -97,7 +89,7 @@ export class StatisticsScreen extends AsyncReactScreen {
97
89
  * One boxed filter chip cycling the three fixed period+granularity presets
98
90
  * (GAIA-548 scope change 7, replacing the former two independent
99
91
  * period/granularity chips of addendum 4). Rendered via
100
- * {@link ChipBadgeStrip} inside `renderReady`, below Overview — see the
92
+ * `filterBadgeStrip` inside `renderReady`, below Overview — see the
101
93
  * override of {@link badges} just below for why.
102
94
  */
103
95
  statisticsBadges() {
@@ -111,13 +103,30 @@ export class StatisticsScreen extends AsyncReactScreen {
111
103
  },
112
104
  ];
113
105
  }
114
- // GAIA-548 addendum 6: AsyncReactScreen's own `badges()` hook renders its
115
- // strip above the ENTIRE react-screen-host content, which would put it
116
- // above Overview too. Suppress it here; `renderReady` renders the same
117
- // data (statisticsBadges()) itself, positioned below Overview.
106
+ // GAIA-548 addendum 6: AsyncScreen's own `badges()` hook renders its strip
107
+ // above the ENTIRE body, which would put it above Overview too. Suppress it
108
+ // here; `renderReady` renders the same data (statisticsBadges()) itself,
109
+ // positioned below Overview.
118
110
  badges() {
119
111
  return [];
120
112
  }
113
+ /**
114
+ * Both grids share one measured width. A report of a new width is drawn on
115
+ * the next turn, never from inside the layout pass that reported it.
116
+ */
117
+ figures(id, groups, context) {
118
+ return StatisticsFigures({
119
+ id,
120
+ groups,
121
+ width: this.gridWidth ?? context.terminalColumns ?? DEFAULT_COLUMNS,
122
+ onResize: (width) => {
123
+ if (width <= 0 || width === this.gridWidth)
124
+ return;
125
+ this.gridWidth = width;
126
+ queueMicrotask(() => this.requestActiveDraw(context));
127
+ },
128
+ });
129
+ }
121
130
  renderReady(value, context) {
122
131
  const overview = this.overviewGroup(value);
123
132
  const groups = this.tabbedGroups(value);
@@ -129,7 +138,26 @@ export class StatisticsScreen extends AsyncReactScreen {
129
138
  tabs: groups.map((group) => ({ id: group.id, label: group.label })),
130
139
  })
131
140
  : undefined;
132
- return (_jsxs("box", { flexDirection: "column", width: "100%", gap: 1, children: [this.readyRows(value, context).map((row) => (_jsx("text", { children: row.text }, row.key))), overview && _jsx(StatisticsFigures, { groups: [overview] }), _jsx(ChipBadgeStrip, { badges: this.statisticsBadges() }), tabBar && _jsx("text", { children: tabBar.map((part) => part.text).join('') }), active && _jsx(StatisticsFigures, { groups: [active] })] }));
141
+ const badges = this.statisticsBadges();
142
+ const line = (id, text) => paragraphNode({ id, runs: [plainRun(text)], fg: FOREGROUND });
143
+ return h(BoxRenderable, {
144
+ id: `${this.id}-statistics`,
145
+ flexDirection: 'column',
146
+ width: '100%',
147
+ gap: 1,
148
+ }, ...this.readyRows(value, context).map((row) => line(`${this.id}-row-${row.key}`, row.text)),
149
+ // Overview renders first, unconditionally, outside the tab group and
150
+ // with no chip bar above it (it reacts to neither period nor
151
+ // granularity) — GAIA-548 addendum 6.
152
+ ...(overview
153
+ ? [this.figures('statistics-overview-grid', [overview], context)]
154
+ : []), ...(badges.length > 0
155
+ ? [filterBadgeStrip(`${this.id}-filter`, badges)]
156
+ : []), ...(tabBar
157
+ ? [
158
+ line(`${this.id}-group-tabs`, tabBar.map((part) => part.text).join('')),
159
+ ]
160
+ : []), ...(active ? [this.figures('statistics-grid', [active], context)] : []));
133
161
  }
134
162
  readyLines(value, context) {
135
163
  const statistics = this.statistics(value);
@@ -160,6 +188,11 @@ export class StatisticsScreen extends AsyncReactScreen {
160
188
  }
161
189
  return super.handleInput(key, context);
162
190
  }
191
+ renderNonInteractive(context) {
192
+ return this.readyValue === undefined
193
+ ? (this.noticeRows(context) ?? [])
194
+ : this.readyLines(this.readyValue, context);
195
+ }
163
196
  leave() {
164
197
  this.period = undefined;
165
198
  this.activeGroupId = undefined;
@@ -57,59 +57,3 @@ export declare const DEFAULT_BACKGROUND: RGBA;
57
57
  * Ordinary cockpit text uses {@link FOREGROUND}.
58
58
  */
59
59
  export declare const DEFAULT_FOREGROUND: RGBA;
60
- /**
61
- * The termcn framework `Theme` object, filled entirely from the cockpit slots
62
- * above — `lib/theme.ts` is the ONLY termcn palette. The vendored default
63
- * (`Vendor/termcn/themes/default.ts`) re-exports this rather than carrying its
64
- * own upstream shadcn hex, so the framework's chrome and our native chrome
65
- * paint the same indexed wires. Status tokens reuse the highlight slot; there
66
- * are no named hues.
67
- */
68
- export declare const TERMCN_THEME: {
69
- border: {
70
- color: RGBA;
71
- focusColor: RGBA;
72
- style: "rounded";
73
- };
74
- colors: {
75
- accent: RGBA;
76
- accentForeground: RGBA;
77
- background: RGBA;
78
- border: RGBA;
79
- error: RGBA;
80
- errorForeground: RGBA;
81
- focusRing: RGBA;
82
- foreground: RGBA;
83
- info: RGBA;
84
- infoForeground: RGBA;
85
- muted: RGBA;
86
- mutedForeground: RGBA;
87
- primary: RGBA;
88
- primaryForeground: RGBA;
89
- secondary: RGBA;
90
- secondaryForeground: RGBA;
91
- selection: RGBA;
92
- selectionForeground: RGBA;
93
- success: RGBA;
94
- successForeground: RGBA;
95
- warning: RGBA;
96
- warningForeground: RGBA;
97
- };
98
- name: string;
99
- spacing: {
100
- 0: number;
101
- 1: number;
102
- 2: number;
103
- 3: number;
104
- 4: number;
105
- 6: number;
106
- 8: number;
107
- };
108
- typography: {
109
- base: string;
110
- bold: boolean;
111
- lg: string;
112
- sm: string;
113
- xl: string;
114
- };
115
- };
@@ -61,45 +61,3 @@ export const DEFAULT_BACKGROUND = RGBA.defaultBackground();
61
61
  * Ordinary cockpit text uses {@link FOREGROUND}.
62
62
  */
63
63
  export const DEFAULT_FOREGROUND = RGBA.defaultForeground();
64
- /**
65
- * The termcn framework `Theme` object, filled entirely from the cockpit slots
66
- * above — `lib/theme.ts` is the ONLY termcn palette. The vendored default
67
- * (`Vendor/termcn/themes/default.ts`) re-exports this rather than carrying its
68
- * own upstream shadcn hex, so the framework's chrome and our native chrome
69
- * paint the same indexed wires. Status tokens reuse the highlight slot; there
70
- * are no named hues.
71
- */
72
- export const TERMCN_THEME = {
73
- border: {
74
- color: BORDER,
75
- focusColor: BORDER_EMPHASIS,
76
- style: 'rounded',
77
- },
78
- colors: {
79
- accent: BORDER,
80
- accentForeground: FOREGROUND_ACTIVE,
81
- background: BACKGROUND,
82
- border: BORDER,
83
- error: FOREGROUND_HIGHLIGHT,
84
- errorForeground: FOREGROUND_ACTIVE,
85
- focusRing: BORDER_EMPHASIS,
86
- foreground: FOREGROUND,
87
- info: FOREGROUND_HIGHLIGHT,
88
- infoForeground: FOREGROUND_ACTIVE,
89
- muted: BACKGROUND_ACTIVE,
90
- mutedForeground: BACKGROUND_ACTIVE,
91
- primary: FOREGROUND_HIGHLIGHT,
92
- primaryForeground: FOREGROUND_ACTIVE,
93
- secondary: FOREGROUND,
94
- secondaryForeground: FOREGROUND_ACTIVE,
95
- selection: FOREGROUND_ACTIVE,
96
- selectionForeground: FOREGROUND_ACTIVE,
97
- success: FOREGROUND_HIGHLIGHT,
98
- successForeground: FOREGROUND_ACTIVE,
99
- warning: FOREGROUND_HIGHLIGHT,
100
- warningForeground: FOREGROUND_ACTIVE,
101
- },
102
- name: 'gaia',
103
- spacing: { 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 6: 6, 8: 8 },
104
- typography: { base: '', bold: true, lg: 'bold', sm: 'dim', xl: 'bold' },
105
- };
@@ -242,3 +242,32 @@ export declare const messageTree: (owner: string, lines: readonly string[]) => V
242
242
  * the content, so nobody has to measure them back off a wrap budget again.
243
243
  */
244
244
  export declare const indentedColumn: (id: string, columns: number, ...children: readonly VNode[]) => VNode;
245
+ /** A UNDERLINE run — a linked value in a key/value list. */
246
+ export declare const UNDERLINE: number;
247
+ /** One unstyled run, so callers never spell the struct for plain text. */
248
+ export declare const plainRun: (text: string, attributes?: number, fg?: RGBA) => StyledRun;
249
+ /**
250
+ * A left press that the node consumes — the presentation-level `onMouseDown`
251
+ * translated once. Propagation stops so the kernel's row binding on an
252
+ * enclosing node (`ScreenPresentation.rowIds`) does not act on the same click.
253
+ */
254
+ export declare const pressHandler: (action: (() => void) | undefined) => ((event: MouseEvent) => void) | undefined;
255
+ export interface ParagraphInput {
256
+ /** Entity-derived and unique in the tree, like every node's. */
257
+ readonly id: string;
258
+ readonly runs: readonly StyledRun[];
259
+ readonly fg: RGBA;
260
+ readonly attributes?: number;
261
+ readonly width?: number | `${number}%` | 'auto';
262
+ readonly wrapMode?: 'word' | 'none';
263
+ readonly flexGrow?: number;
264
+ readonly flexShrink?: number;
265
+ readonly onMouseDown?: (() => void) | undefined;
266
+ }
267
+ /**
268
+ * A paragraph: AUTO height and word-wrapped, unlike {@link textNode}, which is
269
+ * one physical row by contract. Every mutable prop is stated on every call —
270
+ * including an absent click handler — because a patch cannot clear what the
271
+ * next tree omits.
272
+ */
273
+ export declare const paragraphNode: ({ id, runs, fg, attributes, width, wrapMode, flexGrow, flexShrink, onMouseDown, }: ParagraphInput) => VNode;
@@ -484,3 +484,72 @@ export const messageTree = (owner, lines) => columnNode(`${owner}-message`, ...l
484
484
  * the content, so nobody has to measure them back off a wrap budget again.
485
485
  */
486
486
  export const indentedColumn = (id, columns, ...children) => h(BoxRenderable, { id, flexDirection: 'column', flexShrink: 0, paddingLeft: columns }, ...children);
487
+ /** A UNDERLINE run — a linked value in a key/value list. */
488
+ export const UNDERLINE = createTextAttributes({ underline: true });
489
+ /** One unstyled run, so callers never spell the struct for plain text. */
490
+ export const plainRun = (text, attributes = 0, fg) => ({ text, attributes, ...(fg === undefined ? {} : { fg }) });
491
+ /**
492
+ * The last content each paragraph was handed, by node id.
493
+ *
494
+ * `TextRenderable.content` re-buffers its text whenever it is handed a
495
+ * DIFFERENT object, and a string is converted to a new one on every write. A
496
+ * patch restates every prop on every draw, so without this a 500-line
497
+ * description would re-buffer every paragraph on every keypress. Handing back
498
+ * the identical `StyledText` for an unchanged paragraph makes that setter a
499
+ * no-op.
500
+ *
501
+ * ponytail: bounded by clearing, not LRU — a session that opens thousands of
502
+ * tickets rebuilds the cache once per 4096 paragraphs.
503
+ */
504
+ const paragraphContent = new Map();
505
+ const PARAGRAPH_CACHE_LIMIT = 4096;
506
+ const colourKey = (fg) => fg === undefined ? '' : String(fg.buffer);
507
+ const styledRuns = (id, runs) => {
508
+ const key = runs
509
+ .map((run) => `${run.attributes}\u0001${colourKey(run.fg)}\u0001${run.text}`)
510
+ .join('\u0002');
511
+ const cached = paragraphContent.get(id);
512
+ if (cached?.key === key)
513
+ return cached.text;
514
+ if (paragraphContent.size >= PARAGRAPH_CACHE_LIMIT)
515
+ paragraphContent.clear();
516
+ const text = new StyledText(runs.map((run) => ({
517
+ __isChunk: true,
518
+ text: run.text,
519
+ attributes: run.attributes,
520
+ ...(run.fg === undefined ? {} : { fg: run.fg }),
521
+ })));
522
+ paragraphContent.set(id, { key, text });
523
+ return text;
524
+ };
525
+ /**
526
+ * A left press that the node consumes — the presentation-level `onMouseDown`
527
+ * translated once. Propagation stops so the kernel's row binding on an
528
+ * enclosing node (`ScreenPresentation.rowIds`) does not act on the same click.
529
+ */
530
+ export const pressHandler = (action) => action === undefined
531
+ ? undefined
532
+ : (event) => {
533
+ if (!isLeftPress(event))
534
+ return;
535
+ event.stopPropagation();
536
+ action();
537
+ };
538
+ /**
539
+ * A paragraph: AUTO height and word-wrapped, unlike {@link textNode}, which is
540
+ * one physical row by contract. Every mutable prop is stated on every call —
541
+ * including an absent click handler — because a patch cannot clear what the
542
+ * next tree omits.
543
+ */
544
+ export const paragraphNode = ({ id, runs, fg, attributes = 0, width = 'auto', wrapMode = 'word', flexGrow = 0, flexShrink = 1, onMouseDown, }) => h(TextRenderable, {
545
+ id,
546
+ content: styledRuns(id, runs),
547
+ fg,
548
+ attributes,
549
+ width,
550
+ wrapMode,
551
+ flexGrow,
552
+ flexShrink,
553
+ selectable: false,
554
+ onMouseDown: pressHandler(onMouseDown),
555
+ });
package/dist/src/route.js CHANGED
@@ -10,6 +10,7 @@
10
10
  // projects screen — a stderr line and exit 1 (AC-3).
11
11
  import { DrupalJsonApiParams } from 'drupal-jsonapi-params';
12
12
  import { asString } from './lib/format.js';
13
+ import { serverMessage } from './lib/jsonapi-error.js';
13
14
  import { normalizeRepoUrl } from './lib/repo-url.js';
14
15
  /** `" on <baseUrl>"`, or '' when the caller did not supply one. */
15
16
  function on(ctx) {
@@ -67,6 +68,40 @@ async function resolveTicketByIdentifier(client, route, ctx) {
67
68
  }
68
69
  return { screen: 'ticket', ticketId: id, view: route.view ?? 'full' };
69
70
  }
71
+ /**
72
+ * A ticket by its origin issue (GAIA-544). The server owns the grammar: the
73
+ * reference goes out verbatim as `filter[search]`, and an unknown or ambiguous
74
+ * alias comes back as a 400 whose message is passed on unchanged. Several
75
+ * tickets may share one origin; like an identifier collision, that is named
76
+ * with the candidates rather than guessed.
77
+ */
78
+ async function resolveTicketByOrigin(client, route, ctx) {
79
+ const params = new DrupalJsonApiParams();
80
+ params.addFilter('search', route.value);
81
+ params.addFields('gaia_ticket--gaia_ticket', ['identifier', 'title']);
82
+ params.addPageLimit(20);
83
+ const doc = (await client
84
+ .get('gaia_ticket/gaia_ticket', params)
85
+ .catch((error) => {
86
+ throw new Error(`${route.value}${on(ctx)}: ${serverMessage(error)}`);
87
+ }));
88
+ const rows = doc?.data ?? [];
89
+ if (rows.length === 0) {
90
+ throw new Error(`no ticket has the origin ${route.value}${on(ctx)}.`);
91
+ }
92
+ if (rows.length > 1) {
93
+ const candidates = rows
94
+ .map((r) => ` ticket:${r.id} — ${asString(r.attributes?.identifier) ?? ''} ${asString(r.attributes?.title) ?? ''}`)
95
+ .join('\n');
96
+ throw new Error(`${route.value} is the origin of ${rows.length} tickets${on(ctx)}. ` +
97
+ `Name one with 'ticket:<uuid>':\n${candidates}`);
98
+ }
99
+ const id = rows[0]?.id;
100
+ if (!id) {
101
+ throw new Error(`no ticket has the origin ${route.value}${on(ctx)}.`);
102
+ }
103
+ return { screen: 'ticket', ticketId: id, view: route.view ?? 'full' };
104
+ }
70
105
  /**
71
106
  * The repo URLs a `gaia_project` row declares, normalised. `repository` is a
72
107
  * single-cardinality `gaia_repo` field: JSON:API serves it as one object
@@ -206,6 +241,8 @@ export function resolveRoute(client, route, ctx) {
206
241
  return resolveRun(client, route, ctx);
207
242
  if (route.kind === 'project')
208
243
  return resolveProject(client, route, ctx);
244
+ if (route.by === 'origin')
245
+ return resolveTicketByOrigin(client, route, ctx);
209
246
  return route.by === 'uuid'
210
247
  ? resolveTicketByUuid(client, route, ctx)
211
248
  : resolveTicketByIdentifier(client, route, ctx);
@@ -177,7 +177,11 @@ export interface UiInitialRoute {
177
177
  * value is a NORMALISED remote URL (`host/path`), matched client-side against
178
178
  * `gaia_project.repository.url` (spec Decision 3).
179
179
  */
180
- by: 'uuid' | 'identifier' | 'name' | 'repo';
180
+ /**
181
+ * `'origin'` (GAIA-544) is a ticket looked up by its origin issue — `#19`,
182
+ * `gaia#19` or the issue URL — which the server's `filter[search]` resolves.
183
+ */
184
+ by: 'uuid' | 'identifier' | 'name' | 'repo' | 'origin';
181
185
  value: string;
182
186
  /** Drupal view mode for a ticket target; default 'full'. */
183
187
  view?: UiViewMode | undefined;
package/dist/src/ui.d.ts CHANGED
@@ -14,7 +14,6 @@ export { ListCursor } from './lib/cursor.js';
14
14
  export { columnNode, textNode } from './lib/vnode.js';
15
15
  export type { RouteMetadata, ScreenClass, TabMetadata, TabReference, } from './Routing/route.js';
16
16
  export { AsyncScreen } from './Screen/async-screen.js';
17
- export { ReactScreen } from './Screen/react-screen.js';
18
17
  export { type RowActionOptions, runRowAction } from './Screen/row-action.js';
19
18
  export type { Screen, ScreenAction, ScreenContext } from './Screen/screen.js';
20
19
  export type { TicketDetailDocument } from './types.js';
package/dist/src/ui.js CHANGED
@@ -12,5 +12,4 @@ export { copyThrough } from './lib/clipboard.js';
12
12
  export { ListCursor } from './lib/cursor.js';
13
13
  export { columnNode, textNode } from './lib/vnode.js';
14
14
  export { AsyncScreen } from './Screen/async-screen.js';
15
- export { ReactScreen } from './Screen/react-screen.js';
16
15
  export { runRowAction } from './Screen/row-action.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaia-ai/addon-gaia-ui",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
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",
@@ -29,6 +29,6 @@
29
29
  "zustand": "^5.0.15"
30
30
  },
31
31
  "peerDependencies": {
32
- "@gaia-ai/core": "^0.16.0"
32
+ "@gaia-ai/core": "^0.16.2"
33
33
  }
34
34
  }
@@ -1,6 +0,0 @@
1
- import type { HostedAgent } from '../../../types.js';
2
- /** A fixed-column flat table for the Ticket-Agents screen: header + one line per
3
- * live agent (its label · the project/group it runs for). One physical row per
4
- * entry, so the Kernel's `1 + index` selection math holds. The agents are the
5
- * host's live set (from `list(group)`); a finished agent drops out on refresh. */
6
- export declare function agentListRows(agents: HostedAgent[]): string[];
@@ -1,14 +0,0 @@
1
- import { pad } from '../../../lib/format.js';
2
- /** A fixed-column flat table for the Ticket-Agents screen: header + one line per
3
- * live agent (its label · the project/group it runs for). One physical row per
4
- * entry, so the Kernel's `1 + index` selection math holds. The agents are the
5
- * host's live set (from `list(group)`); a finished agent drops out on refresh. */
6
- export function agentListRows(agents) {
7
- const header = `${pad('AGENT', 32)} PROJECT`;
8
- if (agents.length === 0)
9
- return [header, 'No ticket agents.'];
10
- return [
11
- header,
12
- ...agents.map((a) => `${pad(a.label || a.id, 32)} ${a.group}`),
13
- ];
14
- }
@@ -1,6 +0,0 @@
1
- import type { HostedAgent } from '../../types.js';
2
- /** A fixed-column flat table for the Ticket-Agents screen: header + one line per
3
- * live agent (its label · the project/group it runs for). One physical row per
4
- * entry, so the Kernel's `1 + index` selection math holds. The agents are the
5
- * host's live set (from `list(group)`); a finished agent drops out on refresh. */
6
- export declare function agentListRows(agents: HostedAgent[]): string[];
@@ -1,14 +0,0 @@
1
- import { pad } from '../../lib/format.js';
2
- /** A fixed-column flat table for the Ticket-Agents screen: header + one line per
3
- * live agent (its label · the project/group it runs for). One physical row per
4
- * entry, so the Kernel's `1 + index` selection math holds. The agents are the
5
- * host's live set (from `list(group)`); a finished agent drops out on refresh. */
6
- export function agentListRows(agents) {
7
- const header = `${pad('AGENT', 32)} PROJECT`;
8
- if (agents.length === 0)
9
- return [header, 'No ticket agents.'];
10
- return [
11
- header,
12
- ...agents.map((a) => `${pad(a.label || a.id, 32)} ${a.group}`),
13
- ];
14
- }
@@ -1,21 +0,0 @@
1
- /** One collapsible comment entry for the Comments tab. */
2
- export interface CommentItem {
3
- type: string;
4
- author: string;
5
- created: string | null;
6
- bodyLines: string[];
7
- preview: string;
8
- }
9
- /** The rendered Comments-tab view. */
10
- export interface CommentsTabView {
11
- lines: string[];
12
- boldRows: Set<number>;
13
- headerRows: number[];
14
- selectable: number;
15
- }
16
- /** The relative age of a comment's ISO `created`, or '—' when unknown. */
17
- export declare function relativeCreated(created: string | null | undefined, nowSeconds: number): string;
18
- export declare function commentsTabView(items: CommentItem[], { expanded, nowSeconds, }?: {
19
- expanded?: Set<number>;
20
- nowSeconds?: number;
21
- }): CommentsTabView;
@@ -1,39 +0,0 @@
1
- import { humanizeAge } from '../../Deriver/liveness.js';
2
- /** The relative age of a comment's ISO `created`, or '—' when unknown. */
3
- export function relativeCreated(created, nowSeconds) {
4
- const ms = created ? Date.parse(created) : Number.NaN;
5
- return Number.isNaN(ms)
6
- ? '—'
7
- : humanizeAge(Math.floor(ms / 1000), nowSeconds);
8
- }
9
- // The Comments tab: one collapsible entry per comment. Collapsed = a single
10
- // caret header row (`▸ type · author · age · preview`); expanded (`▾`) appends
11
- // the full body, one physical row per body line. `headerRows[i]` is the row
12
- // index of comment i's header (for selection highlight + scroll).
13
- export function commentsTabView(items, { expanded = new Set(), nowSeconds = 0, } = {}) {
14
- if (!items.length) {
15
- return {
16
- lines: ['No comments.'],
17
- boldRows: new Set(),
18
- headerRows: [],
19
- selectable: 0,
20
- };
21
- }
22
- const lines = [];
23
- const boldRows = new Set();
24
- const headerRows = [];
25
- items.forEach((item, i) => {
26
- headerRows.push(lines.length);
27
- boldRows.add(lines.length);
28
- const caret = expanded.has(i) ? '▾' : '▸';
29
- const age = relativeCreated(item.created, nowSeconds);
30
- const author = item.author || '—';
31
- lines.push(`${caret} ${item.type} · ${author} · ${age} · ${item.preview}`);
32
- if (expanded.has(i)) {
33
- for (const bodyLine of item.bodyLines)
34
- lines.push(` ${bodyLine}`);
35
- lines.push('');
36
- }
37
- });
38
- return { lines, boldRows, headerRows, selectable: items.length };
39
- }
@@ -1,3 +0,0 @@
1
- import type { CreateFormOptions, CreateFormState } from '../../Service/create-form.js';
2
- /** Render the "New ticket" popup form as plain rows (the kernel frames it). */
3
- export declare function createFormLines(s: CreateFormState, opts: CreateFormOptions): string[];
@@ -1,26 +0,0 @@
1
- const labelOf = (list, id) => list.find((o) => o.id === id)?.label ?? '—';
2
- /** Render the "New ticket" popup form as plain rows (the kernel frames it). */
3
- export function createFormLines(s, opts) {
4
- const mark = (i) => (s.active === i ? '▸ ' : ' ');
5
- const lines = [];
6
- lines.push(`${mark(0)}Description:`);
7
- const body = s.description.length ? s.description.split('\n') : ['<empty>'];
8
- for (const line of body)
9
- lines.push(` ${line}`);
10
- lines.push(`${mark(1)}Assignee: < ${labelOf(opts.users, s.assignee)} >`);
11
- lines.push(`${mark(2)}Conductor: < ${labelOf(opts.conductors, s.conductor)} >`);
12
- lines.push(`${mark(3)}Labels:`);
13
- if (opts.labels.length === 0) {
14
- lines.push(' (none available)');
15
- }
16
- else {
17
- opts.labels.forEach((o, i) => {
18
- const box = s.labels.has(o.id) ? '[x]' : '[ ]';
19
- const cursor = s.active === 3 && s.labelCursor === i ? '›' : ' ';
20
- lines.push(` ${cursor}${box} ${o.label}`);
21
- });
22
- }
23
- lines.push(`${mark(4)}Workflow: < ${labelOf(opts.workflows, s.workflow)} >`);
24
- lines.push(`${mark(5)}[ Create ticket ]`);
25
- return lines;
26
- }
@@ -1,8 +0,0 @@
1
- /** The minimum shape a dashboard row needs to render. */
2
- export interface DashboardRowInput {
3
- project: string;
4
- derivedStatus: string;
5
- ageLabel: string;
6
- }
7
- /** One line per project: name, derived status, relative age. */
8
- export declare function dashboardRows(projects: DashboardRowInput[]): string[];
@@ -1,7 +0,0 @@
1
- import { pad } from '../../lib/format.js';
2
- /** One line per project: name, derived status, relative age. */
3
- export function dashboardRows(projects) {
4
- if (projects.length === 0)
5
- return ['No projects.'];
6
- return projects.map((p) => `${pad(p.project, 24)} ${pad(p.derivedStatus, 14)} ${p.ageLabel}`);
7
- }