@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,15 @@
1
- import { type ReactNode } from 'react';
2
1
  import { type ActionItem } from '../../../Component/footer-actions/index.js';
3
2
  import { AsyncScreen, type FullScreenChrome } from '../../../Screen/async-screen.js';
4
- import { ReactScreen } from '../../../Screen/react-screen.js';
5
- import type { ScreenAction, ScreenContext, ScreenId, ScreenPresentation } from '../../../Screen/screen.js';
3
+ import type { ScreenAction, ScreenContext, ScreenId } from '../../../Screen/screen.js';
6
4
  import type { TicketDetailDocument, UiViewMode } from '../../../types.js';
7
5
  import { type TicketContentProps } from '../Component/ticket-content/index.js';
8
6
  import { type TicketContentTab } from '../Component/ticket-content/model.js';
9
7
  import { type AssignmentLabels } from '../Component/ticket-detail/index.js';
10
- /** Owns ticket content load, selection and chrome; the wrapper paints React. */
11
- export declare abstract class TicketContentController extends AsyncScreen<TicketDetailDocument> {
8
+ /**
9
+ * Owns ticket content load, selection, chrome and paint for the three ticket
10
+ * document routes. Route subclasses supply the tab, includes and pipe output.
11
+ */
12
+ export declare abstract class TicketContentScreen extends AsyncScreen<TicketDetailDocument> {
12
13
  abstract readonly id: ScreenId;
13
14
  abstract readonly tab: TicketContentTab;
14
15
  readonly frameId = "ticket";
@@ -20,9 +21,19 @@ export declare abstract class TicketContentController extends AsyncScreen<Ticket
20
21
  private releaseStore;
21
22
  private expanded;
22
23
  protected viewMode: UiViewMode;
24
+ /** Visit-local: the sidebar pane and Planning document tab last chosen. */
25
+ private sidebarTab;
26
+ private documentKey;
27
+ private modelMemo;
23
28
  compact(): boolean;
24
29
  get document(): TicketDetailDocument | undefined;
25
30
  message(context: ScreenContext): string[] | undefined;
31
+ /**
32
+ * What the content tree is built from. Every callback is guarded by the
33
+ * visit it was built for — a click that lands after the route was left
34
+ * changes nothing — and one press that calls several of them (select, then
35
+ * toggle) paints once.
36
+ */
26
37
  contentProps(context: ScreenContext): TicketContentProps;
27
38
  reload(context: ScreenContext): void;
28
39
  enter(context: ScreenContext): void;
@@ -41,6 +52,8 @@ export declare abstract class TicketContentController extends AsyncScreen<Ticket
41
52
  actionForInput(input: string, context: ScreenContext): ScreenAction | undefined;
42
53
  receiveResult(result: unknown, context: ScreenContext): void;
43
54
  protected renderReady(document: TicketDetailDocument, context: ScreenContext): import("@opentui/core").VNode<any, import("@opentui/core").VChild[]>;
55
+ /** The full document reads as content inside the frame; compact is flush. */
56
+ protected flushBody(): boolean;
44
57
  protected chrome(context: ScreenContext): FullScreenChrome;
45
58
  protected actions(context: ScreenContext): readonly ActionItem[];
46
59
  protected notice(): string | undefined;
@@ -49,44 +62,12 @@ export declare abstract class TicketContentController extends AsyncScreen<Ticket
49
62
  protected abstract fullNonInteractive(document: TicketDetailDocument, context: ScreenContext): string[];
50
63
  private initialize;
51
64
  private syncModel;
65
+ /**
66
+ * The projection of one document, built once per document, labels and
67
+ * second. A keypress reads it from input handling, selection, chrome,
68
+ * actions and the body; parsing every body that many times per frame was
69
+ * most of a keypress's cost on a long ticket.
70
+ */
71
+ private modelOf;
52
72
  private selectedIndexOf;
53
73
  }
54
- /** Ticket-local ReactScreen adapter. Forwards AsyncScreen lifecycle by delegation. */
55
- export declare abstract class TicketContentScreen extends ReactScreen {
56
- abstract readonly id: ScreenId;
57
- readonly frameId = "ticket";
58
- protected abstract readonly controller: TicketContentController;
59
- get document(): TicketDetailDocument | undefined;
60
- get state(): import("../../../Screen/async-screen.js").AsyncScreenState;
61
- get busy(): boolean;
62
- enter(context: ScreenContext): void;
63
- leave(): void;
64
- reset(): void;
65
- handleInput(input: string, context?: ScreenContext): boolean;
66
- reload(context: ScreenContext): void;
67
- setLabels(labels: AssignmentLabels | undefined): void;
68
- select(index: number, context: ScreenContext): boolean;
69
- activate(index: number, context: ScreenContext): boolean;
70
- saveState(): {
71
- readonly selectedKey?: string;
72
- };
73
- restoreState(token: unknown): void;
74
- commandForInput(input: string): string | undefined;
75
- actionForInput(input: string, context: ScreenContext): ScreenAction | undefined;
76
- receiveResult(result: unknown, context: ScreenContext): void;
77
- renderNonInteractive(context: ScreenContext): string[];
78
- present(context: ScreenContext): ScreenPresentation;
79
- protected render(context: ScreenContext): ReactNode;
80
- protected chrome(context: ScreenContext): {
81
- selectable: number;
82
- selectedIndex: number;
83
- rowIds?: readonly string[];
84
- cursorMode?: "select" | "scroll";
85
- title?: string;
86
- meta?: string;
87
- header?: import("@opentui/core").VNode;
88
- status?: string;
89
- toast?: string;
90
- actions?: readonly ActionItem[];
91
- };
92
- }
@@ -1,17 +1,20 @@
1
- import { createElement } from 'react';
2
1
  import { actionItem, } from '../../../Component/footer-actions/index.js';
3
2
  import { copyThrough } from '../../../lib/clipboard.js';
4
- import { BORDER, columnNode, textNode } from '../../../lib/vnode.js';
3
+ import { BORDER, textNode } from '../../../lib/vnode.js';
5
4
  import { AsyncScreen, } from '../../../Screen/async-screen.js';
6
- import { ReactScreen } from '../../../Screen/react-screen.js';
7
5
  import { TicketContent, } from '../Component/ticket-content/index.js';
8
6
  import { initialExpandedKeys, resolveSelectedKey, retainExpandedKeys, ticketContentModel, ticketContentRowId, } from '../Component/ticket-content/model.js';
9
7
  import { handleTicketCopy, ticketCopyActions, ticketEditAction, ticketHeaderTree, ticketTitleOf, } from '../Component/ticket-detail/index.js';
10
8
  import { ticketTeaserLines, ticketTeaserTree, } from '../Component/ticket-teaser/index.js';
11
9
  import { ticketDetailInputContext } from '../Data/input.js';
12
10
  import { loadTicketView, readTicket, refreshTicket, saveTicket, ticketLoaded, watchTicket, } from '../Data/store.js';
13
- /** Owns ticket content load, selection and chrome; the wrapper paints React. */
14
- export class TicketContentController extends AsyncScreen {
11
+ /** Overview width when the context carries none — a pipe has no terminal. */
12
+ const DEFAULT_COLUMNS = 80;
13
+ /**
14
+ * Owns ticket content load, selection, chrome and paint for the three ticket
15
+ * document routes. Route subclasses supply the tab, includes and pipe output.
16
+ */
17
+ export class TicketContentScreen extends AsyncScreen {
15
18
  frameId = 'ticket';
16
19
  selectedKey;
17
20
  initializedKey;
@@ -20,6 +23,10 @@ export class TicketContentController extends AsyncScreen {
20
23
  releaseStore;
21
24
  expanded = new Set();
22
25
  viewMode = 'full';
26
+ /** Visit-local: the sidebar pane and Planning document tab last chosen. */
27
+ sidebarTab;
28
+ documentKey;
29
+ modelMemo;
23
30
  compact() {
24
31
  return this.viewMode !== 'full';
25
32
  }
@@ -29,28 +36,59 @@ export class TicketContentController extends AsyncScreen {
29
36
  message(context) {
30
37
  return this.noticeRows(context);
31
38
  }
39
+ /**
40
+ * What the content tree is built from. Every callback is guarded by the
41
+ * visit it was built for — a click that lands after the route was left
42
+ * changes nothing — and one press that calls several of them (select, then
43
+ * toggle) paints once.
44
+ */
32
45
  contentProps(context) {
33
46
  const model = this.syncModel(context);
34
47
  const selectedKey = this.selectedKey;
48
+ let drawQueued = false;
49
+ const live = (action) => {
50
+ if (!context.isActive())
51
+ return;
52
+ action();
53
+ if (drawQueued)
54
+ return;
55
+ drawQueued = true;
56
+ queueMicrotask(() => {
57
+ drawQueued = false;
58
+ context.requestDraw();
59
+ });
60
+ };
35
61
  return {
62
+ id: 'ticket-content',
36
63
  model,
37
64
  tab: this.tab,
38
65
  ...(selectedKey === undefined ? {} : { selectedKey }),
39
66
  expandedKeys: this.expanded,
40
- select: (key) => {
67
+ terminalColumns: context.terminalColumns ?? DEFAULT_COLUMNS,
68
+ ...(this.sidebarTab === undefined ? {} : { sidebarTab: this.sidebarTab }),
69
+ ...(this.documentKey === undefined
70
+ ? {}
71
+ : { documentKey: this.documentKey }),
72
+ onSidebarTab: (tab) => live(() => {
73
+ this.sidebarTab = tab;
74
+ }),
75
+ onDocumentTab: (key) => live(() => {
76
+ this.documentKey = key;
77
+ }),
78
+ select: (key) => live(() => {
41
79
  this.selectedKey = key;
42
- context.requestDraw();
43
- },
44
- toggle: (key) => {
80
+ }),
81
+ toggle: (key) => live(() => {
45
82
  const next = new Set(this.expanded);
46
83
  if (next.has(key))
47
84
  next.delete(key);
48
85
  else
49
86
  next.add(key);
50
87
  this.expanded = next;
51
- context.requestDraw();
52
- },
88
+ }),
53
89
  activateProperty: (item) => {
90
+ if (!context.isActive())
91
+ return;
54
92
  if (item.ticketId !== undefined) {
55
93
  context.navigate?.('ticket.detail', { ticketId: item.ticketId });
56
94
  }
@@ -69,6 +107,8 @@ export class TicketContentController extends AsyncScreen {
69
107
  }
70
108
  enter(context) {
71
109
  this.releaseStore?.();
110
+ this.sidebarTab = undefined;
111
+ this.documentKey = undefined;
72
112
  super.enter(context);
73
113
  if (context.contexts?.has(ticketDetailInputContext)) {
74
114
  const document = readTicket(context);
@@ -195,7 +235,11 @@ export class TicketContentController extends AsyncScreen {
195
235
  this.initialize(document, context);
196
236
  return this.viewMode !== 'full'
197
237
  ? ticketTeaserTree(document, this.viewMode)
198
- : columnNode('ticket-controller-placeholder');
238
+ : TicketContent(this.contentProps(context));
239
+ }
240
+ /** The full document reads as content inside the frame; compact is flush. */
241
+ flushBody() {
242
+ return this.viewMode !== 'full';
199
243
  }
200
244
  chrome(context) {
201
245
  const document = this.document;
@@ -247,6 +291,9 @@ export class TicketContentController extends AsyncScreen {
247
291
  this.releaseStore?.();
248
292
  this.releaseStore = undefined;
249
293
  super.reset();
294
+ this.modelMemo = undefined;
295
+ this.sidebarTab = undefined;
296
+ this.documentKey = undefined;
250
297
  this.selectedKey = undefined;
251
298
  this.initializedKey = undefined;
252
299
  this.labels = undefined;
@@ -264,7 +311,7 @@ export class TicketContentController extends AsyncScreen {
264
311
  }
265
312
  initialize(document, context) {
266
313
  const identity = document.data?.id ?? document;
267
- const model = ticketContentModel(document, this.tab, this.labels, context.nowSeconds?.() ?? Math.floor(Date.now() / 1000));
314
+ const model = this.modelOf(document, context);
268
315
  if (this.initializedKey === identity) {
269
316
  this.selectedKey = resolveSelectedKey(model.selectableKeys, this.selectedKey);
270
317
  this.expanded = retainExpandedKeys(model.selectableKeys, this.expanded);
@@ -278,7 +325,25 @@ export class TicketContentController extends AsyncScreen {
278
325
  syncModel(context) {
279
326
  const document = this.document ?? {};
280
327
  this.initialize(document, context);
281
- return ticketContentModel(document, this.tab, this.labels, context.nowSeconds?.() ?? Math.floor(Date.now() / 1000));
328
+ return this.modelOf(document, context);
329
+ }
330
+ /**
331
+ * The projection of one document, built once per document, labels and
332
+ * second. A keypress reads it from input handling, selection, chrome,
333
+ * actions and the body; parsing every body that many times per frame was
334
+ * most of a keypress's cost on a long ticket.
335
+ */
336
+ modelOf(document, context) {
337
+ const now = context.nowSeconds?.() ?? Math.floor(Date.now() / 1000);
338
+ const memo = this.modelMemo;
339
+ if (memo !== undefined &&
340
+ memo.document === document &&
341
+ memo.labels === this.labels &&
342
+ memo.now === now)
343
+ return memo.model;
344
+ const model = ticketContentModel(document, this.tab, this.labels, now);
345
+ this.modelMemo = { document, labels: this.labels, now, model };
346
+ return model;
282
347
  }
283
348
  selectedIndexOf(keys) {
284
349
  if (this.selectedKey === undefined)
@@ -287,76 +352,3 @@ export class TicketContentController extends AsyncScreen {
287
352
  return index < 0 ? 0 : index;
288
353
  }
289
354
  }
290
- /** Ticket-local ReactScreen adapter. Forwards AsyncScreen lifecycle by delegation. */
291
- export class TicketContentScreen extends ReactScreen {
292
- frameId = 'ticket';
293
- get document() {
294
- return this.controller.document;
295
- }
296
- get state() {
297
- return this.controller.state;
298
- }
299
- get busy() {
300
- return this.controller.busy;
301
- }
302
- // ReactScreen.enter only requestDraw; load/store guards stay on the controller.
303
- enter(context) {
304
- this.controller.enter(context);
305
- }
306
- leave() {
307
- this.controller.leave();
308
- }
309
- reset() {
310
- this.controller.reset();
311
- }
312
- handleInput(input, context) {
313
- if (super.handleInput(input))
314
- return true;
315
- return context ? this.controller.handleInput(input, context) : false;
316
- }
317
- reload(context) {
318
- this.controller.reload(context);
319
- }
320
- setLabels(labels) {
321
- this.controller.setLabels(labels);
322
- }
323
- select(index, context) {
324
- return this.controller.select(index, context);
325
- }
326
- activate(index, context) {
327
- return this.controller.activate(index, context);
328
- }
329
- saveState() {
330
- return this.controller.saveState();
331
- }
332
- restoreState(token) {
333
- this.controller.restoreState(token);
334
- }
335
- commandForInput(input) {
336
- return this.controller.commandForInput(input);
337
- }
338
- actionForInput(input, context) {
339
- return this.controller.actionForInput(input, context);
340
- }
341
- receiveResult(result, context) {
342
- this.controller.receiveResult(result, context);
343
- }
344
- renderNonInteractive(context) {
345
- return this.controller.renderNonInteractive(context);
346
- }
347
- present(context) {
348
- if (this.controller.compact())
349
- return this.controller.present(context);
350
- return super.present(context);
351
- }
352
- render(context) {
353
- const message = this.controller.message(context);
354
- return message
355
- ? createElement('text', { wrapMode: 'word' }, message.join('\n'))
356
- : createElement(TicketContent, this.controller.contentProps(context));
357
- }
358
- chrome(context) {
359
- const { body, reactBody, ...chrome } = this.controller.present(context);
360
- return chrome;
361
- }
362
- }
@@ -1,17 +1,9 @@
1
1
  import type { ScreenContext } from '../../../Screen/screen.js';
2
2
  import type { TicketDetailDocument } from '../../../types.js';
3
- import { TicketContentController, TicketContentScreen } from './ticket-content-screen.js';
3
+ import { TicketContentScreen } from './ticket-content-screen.js';
4
4
  export type { TicketDetailInput } from '../Data/input.js';
5
5
  export { ticketDetailInputContext } from '../Data/input.js';
6
- /** Owns the ticket's overview view and its interaction state. */
7
- declare class TicketOverviewController extends TicketContentController {
8
- readonly id: string;
9
- readonly tab = "overview";
10
- protected readonly includes: string[];
11
- protected renderLoading(): string[];
12
- protected fullNonInteractive(document: TicketDetailDocument, context: ScreenContext): string[];
13
- }
14
- /** Public Overview route: ReactScreen wrapper around the overview controller. */
6
+ /** Public Overview route: the ticket's overview view and its interaction state. */
15
7
  export declare class TicketDetailScreen extends TicketContentScreen {
16
8
  static readonly route: {
17
9
  id: string;
@@ -34,5 +26,8 @@ export declare class TicketDetailScreen extends TicketContentScreen {
34
26
  context: (params: Readonly<Record<string, string>>, state: unknown, base: import("../../../Routing/context.js").ContextBag) => import("../../../Routing/context.js").ContextBag;
35
27
  };
36
28
  readonly id: string;
37
- protected readonly controller: TicketOverviewController;
29
+ readonly tab = "overview";
30
+ protected readonly includes: string[];
31
+ protected renderLoading(): string[];
32
+ protected fullNonInteractive(document: TicketDetailDocument, context: ScreenContext): string[];
38
33
  }
@@ -1,7 +1,7 @@
1
1
  import { flattenSections } from '../../../Component/section-list/index.js';
2
2
  import { detailSections, sectionsForTab, } from '../Component/ticket-detail/index.js';
3
3
  import { ticketInputContext } from '../Data/input.js';
4
- import { TicketContentController, TicketContentScreen, } from './ticket-content-screen.js';
4
+ import { TicketContentScreen } from './ticket-content-screen.js';
5
5
  export { ticketDetailInputContext } from '../Data/input.js';
6
6
  const INCLUDES = [
7
7
  'labels',
@@ -18,23 +18,7 @@ const INCLUDES = [
18
18
  'sub_tickets',
19
19
  'referenced_tickets',
20
20
  ];
21
- /** Owns the ticket's overview view and its interaction state. */
22
- class TicketOverviewController extends TicketContentController {
23
- id = TicketDetailScreen.route.id;
24
- tab = 'overview';
25
- includes = INCLUDES;
26
- renderLoading() {
27
- return ['Loading ticket…'];
28
- }
29
- fullNonInteractive(document, context) {
30
- const nowSeconds = context.nowSeconds?.() ?? Math.floor(Date.now() / 1000);
31
- return flattenSections([
32
- ...detailSections(document, this.labels, nowSeconds).filter((section) => section.heading === 'State' || section.heading === 'Labels'),
33
- ...sectionsForTab(document, 0, this.labels, nowSeconds),
34
- ]).lines;
35
- }
36
- }
37
- /** Public Overview route: ReactScreen wrapper around the overview controller. */
21
+ /** Public Overview route: the ticket's overview view and its interaction state. */
38
22
  export class TicketDetailScreen extends TicketContentScreen {
39
23
  static route = {
40
24
  id: 'ticket.detail',
@@ -49,5 +33,16 @@ export class TicketDetailScreen extends TicketContentScreen {
49
33
  context: ticketInputContext,
50
34
  };
51
35
  id = TicketDetailScreen.route.id;
52
- controller = new TicketOverviewController();
36
+ tab = 'overview';
37
+ includes = INCLUDES;
38
+ renderLoading() {
39
+ return ['Loading ticket…'];
40
+ }
41
+ fullNonInteractive(document, context) {
42
+ const nowSeconds = context.nowSeconds?.() ?? Math.floor(Date.now() / 1000);
43
+ return flattenSections([
44
+ ...detailSections(document, this.labels, nowSeconds).filter((section) => section.heading === 'State' || section.heading === 'Labels'),
45
+ ...sectionsForTab(document, 0, this.labels, nowSeconds),
46
+ ]).lines;
47
+ }
53
48
  }
@@ -105,18 +105,11 @@ export class TicketFilterBar {
105
105
  }
106
106
  return false;
107
107
  }
108
- if (!this.disable.has('search') && input === '/') {
109
- return true;
110
- }
111
108
  return false;
112
109
  }
113
110
  applyTo(row) {
114
- const query = this.host.criteria.query.toLocaleLowerCase();
115
- return ((!query ||
116
- row.identifier.toLocaleLowerCase().includes(query) ||
117
- row.title.toLocaleLowerCase().includes(query)) &&
118
- (this.host.criteria.state === null ||
119
- row.state === this.host.criteria.state) &&
111
+ return ((this.host.criteria.state === null ||
112
+ row.state === this.host.criteria.state) &&
120
113
  (this.host.criteria.closed === null ||
121
114
  row.closed === this.host.criteria.closed) &&
122
115
  (this.host.criteria.parent === null ||
@@ -1,15 +1,7 @@
1
1
  import type { ScreenContext } from '../../../Screen/screen.js';
2
2
  import type { TicketDetailDocument } from '../../../types.js';
3
- import { TicketContentController, TicketContentScreen } from './ticket-content-screen.js';
4
- /** Owns the ticket's planning view and its interaction state. */
5
- declare class TicketPlanningController extends TicketContentController {
6
- readonly id: string;
7
- readonly tab = "planning";
8
- protected readonly includes: string[];
9
- protected renderLoading(): string[];
10
- protected fullNonInteractive(document: TicketDetailDocument, context: ScreenContext): string[];
11
- }
12
- /** Public Planning route: ReactScreen wrapper around the planning controller. */
3
+ import { TicketContentScreen } from './ticket-content-screen.js';
4
+ /** Public Planning route: the ticket's planning view and its interaction state. */
13
5
  export declare class TicketPlanningScreen extends TicketContentScreen {
14
6
  static readonly route: {
15
7
  id: string;
@@ -27,6 +19,8 @@ export declare class TicketPlanningScreen extends TicketContentScreen {
27
19
  context: (params: Readonly<Record<string, string>>, state: unknown, base: import("../../../Routing/context.js").ContextBag) => import("../../../Routing/context.js").ContextBag;
28
20
  };
29
21
  readonly id: string;
30
- protected readonly controller: TicketPlanningController;
22
+ readonly tab = "planning";
23
+ protected readonly includes: string[];
24
+ protected renderLoading(): string[];
25
+ protected fullNonInteractive(document: TicketDetailDocument, context: ScreenContext): string[];
31
26
  }
32
- export {};
@@ -1,10 +1,18 @@
1
1
  import { flattenSections } from '../../../Component/section-list/index.js';
2
2
  import { ticketContentModel } from '../Component/ticket-content/model.js';
3
3
  import { ticketInputContext } from '../Data/input.js';
4
- import { TicketContentController, TicketContentScreen, } from './ticket-content-screen.js';
4
+ import { TicketContentScreen } from './ticket-content-screen.js';
5
5
  const INCLUDES = ['comments', 'labels'];
6
- /** Owns the ticket's planning view and its interaction state. */
7
- class TicketPlanningController extends TicketContentController {
6
+ /** Public Planning route: the ticket's planning view and its interaction state. */
7
+ export class TicketPlanningScreen extends TicketContentScreen {
8
+ static route = {
9
+ id: 'ticket.planning',
10
+ path: '/tickets/:ticketId/planning',
11
+ parent: 'project.tickets',
12
+ tab: { id: 'planning', label: 'Planning', scope: 'ticket', weight: 15 },
13
+ fallback: { routeId: 'my.work' },
14
+ context: ticketInputContext,
15
+ };
8
16
  id = TicketPlanningScreen.route.id;
9
17
  tab = 'planning';
10
18
  includes = INCLUDES;
@@ -19,16 +27,3 @@ class TicketPlanningController extends TicketContentController {
19
27
  }))).lines;
20
28
  }
21
29
  }
22
- /** Public Planning route: ReactScreen wrapper around the planning controller. */
23
- export class TicketPlanningScreen extends TicketContentScreen {
24
- static route = {
25
- id: 'ticket.planning',
26
- path: '/tickets/:ticketId/planning',
27
- parent: 'project.tickets',
28
- tab: { id: 'planning', label: 'Planning', scope: 'ticket', weight: 15 },
29
- fallback: { routeId: 'my.work' },
30
- context: ticketInputContext,
31
- };
32
- id = TicketPlanningScreen.route.id;
33
- controller = new TicketPlanningController();
34
- }
@@ -1,7 +1,8 @@
1
- import { ReactScreen } from '../../../Screen/react-screen.js';
1
+ import { type VNode } from '@opentui/core';
2
+ import { AsyncScreen, type FullScreenChrome } from '../../../Screen/async-screen.js';
2
3
  import type { ScreenContext } from '../../../Screen/screen.js';
3
4
  /** A separate document screen using the kernel's existing scrolling viewport. */
4
- export declare class TicketReactMarkdownScreen extends ReactScreen {
5
+ export declare class TicketReactMarkdownScreen extends AsyncScreen<true> {
5
6
  static readonly route: {
6
7
  id: string;
7
8
  path: string;
@@ -19,10 +20,11 @@ export declare class TicketReactMarkdownScreen extends ReactScreen {
19
20
  };
20
21
  readonly id: string;
21
22
  readonly frameId = "ticket";
22
- protected render(context: ScreenContext): import("react").ReactNode;
23
- protected chrome(context: ScreenContext): {
24
- title: string;
25
- header: import("@opentui/core").VNode<any, import("@opentui/core").VChild[]>;
26
- };
23
+ protected load(): Promise<true>;
24
+ enter(context: ScreenContext): void;
25
+ protected renderLoading(): string[];
26
+ protected flushBody(): boolean;
27
+ protected renderReady(_data: true, context: ScreenContext): VNode;
28
+ protected chrome(context: ScreenContext): FullScreenChrome;
27
29
  renderNonInteractive(context: ScreenContext): string[];
28
30
  }
@@ -1,10 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
- import { useTerminalDimensions } from '@opentui/react';
3
- import { ReactScreen } from '../../../Screen/react-screen.js';
4
- import { Markdown } from '../../../Vendor/termcn/ui/markdown.js';
1
+ import { BoxRenderable, h } from '@opentui/core';
2
+ import { Markdown } from '../../../Component/markdown/index.js';
3
+ import { FOREGROUND, paragraphNode, plainRun } from '../../../lib/vnode.js';
4
+ import { AsyncScreen, } from '../../../Screen/async-screen.js';
5
5
  import { ticketHeaderTree, ticketTitleOf, } from '../Component/ticket-detail/index.js';
6
6
  import { ticketInputContext } from '../Data/input.js';
7
7
  import { readTicket } from '../Data/store.js';
8
+ const ID = 'ticket-markdown-demo';
8
9
  function ticketDescription(context) {
9
10
  const description = readTicket(context).data?.attributes?.description;
10
11
  if (typeof description === 'string')
@@ -24,10 +25,10 @@ const EXAMPLE = [
24
25
  '> The native ticket header and tabs stay visible while the document scrolls.',
25
26
  '',
26
27
  '- **Strong emphasis**, *italic text* and `inline code`.',
27
- '- A [termcn link](https://termcn.dev) remains visible with its destination.',
28
+ '- A [documentation link](https://example.test/docs) remains visible with its destination.',
28
29
  '',
29
30
  '```ts',
30
- 'const screen = "ReactScreen";',
31
+ 'const screen = "NativeScreen";',
31
32
  'render({ statistics: true, markdown: true });',
32
33
  '```',
33
34
  '',
@@ -44,12 +45,8 @@ const EXAMPLE = [
44
45
  '',
45
46
  'You reached the bottom. Scroll back up to the actual ticket description.',
46
47
  ].join('\n');
47
- function Preview({ description }) {
48
- const { width } = useTerminalDimensions();
49
- return (_jsxs("box", { flexDirection: "column", gap: 1, children: [_jsx("text", { children: "Markdown preview \u00B7 \u2191\u2193 / j,k / mouse wheel: scroll" }), _jsx(Markdown, { width: Math.max(16, width - 12), children: `# Ticket description\n\n${description}\n\n---\n\n${EXAMPLE}` })] }));
50
- }
51
48
  /** A separate document screen using the kernel's existing scrolling viewport. */
52
- export class TicketReactMarkdownScreen extends ReactScreen {
49
+ export class TicketReactMarkdownScreen extends AsyncScreen {
53
50
  static route = {
54
51
  id: 'ticket.react-markdown',
55
52
  path: '/tickets/:ticketId/react-markdown',
@@ -65,8 +62,34 @@ export class TicketReactMarkdownScreen extends ReactScreen {
65
62
  };
66
63
  id = TicketReactMarkdownScreen.route.id;
67
64
  frameId = 'ticket';
68
- render(context) {
69
- return _jsx(Preview, { description: ticketDescription(context) });
65
+ load() {
66
+ return Promise.resolve(true);
67
+ }
68
+ enter(context) {
69
+ super.enter(context);
70
+ // The document is the ticket already in context: ready at once.
71
+ if (this.state !== 'ready') {
72
+ this.replaceReady(true);
73
+ context.requestDraw();
74
+ }
75
+ }
76
+ renderLoading() {
77
+ return ['Loading Markdown preview…'];
78
+ }
79
+ flushBody() {
80
+ return false;
81
+ }
82
+ renderReady(_data, context) {
83
+ const width = context.terminalColumns ?? 80;
84
+ return h(BoxRenderable, { id: ID, flexDirection: 'column', gap: 1 }, paragraphNode({
85
+ id: `${ID}-hint`,
86
+ runs: [plainRun('Markdown preview · ↑↓ / j,k / mouse wheel: scroll')],
87
+ fg: FOREGROUND,
88
+ }), Markdown({
89
+ id: `${ID}-document`,
90
+ width: Math.max(16, width - 12),
91
+ source: `# Ticket description\n\n${ticketDescription(context)}\n\n---\n\n${EXAMPLE}`,
92
+ }));
70
93
  }
71
94
  chrome(context) {
72
95
  const document = readTicket(context);