@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
@@ -100,10 +100,13 @@ export function buildTeamMemberTicketQueryParams(userId, projectIds, criteria, o
100
100
  * open only, newest-changed first. Not project-scoped: a conductor may serve
101
101
  * more than one project.
102
102
  */
103
- export function buildConductorTicketQueryParams(conductorId, page = { size: PAGE_SIZE, index: 0 }) {
103
+ export function buildConductorTicketQueryParams(conductorId, page = { size: PAGE_SIZE, index: 0 }, query = '') {
104
104
  const params = new DrupalJsonApiParams();
105
105
  params.addFilter('conductor_id.id', conductorId);
106
106
  params.addFilter('closed', '0');
107
+ // The `/` line, resolved server-side exactly as on the project list.
108
+ if (query)
109
+ params.addFilter('search', query);
107
110
  params.addInclude(['parent_id', 'conductor_id']);
108
111
  params.addFields('gaia_conductor--gaia_conductor', TICKET_CONDUCTOR_INCLUDE_FIELDS);
109
112
  params.addFields('gaia_ticket--gaia_ticket', TICKET_LIST_FIELDS);
@@ -115,11 +118,12 @@ export function buildConductorTicketQueryParams(conductorId, page = { size: PAGE
115
118
  return params;
116
119
  }
117
120
  function applyTicketCriteria(params, criteria, options) {
118
- if (criteria.query) {
119
- params.addGroup('search', 'OR');
120
- params.addFilter('identifier', criteria.query, 'CONTAINS', 'search');
121
- params.addFilter('title', criteria.query, 'CONTAINS', 'search');
122
- }
121
+ // The typed line goes to the server verbatim (GAIA-544): it resolves
122
+ // `#19`, `gaia#19` and origin URLs to their tickets, and turns anything else
123
+ // into the identifier OR title CONTAINS match this used to build. The grammar
124
+ // lives once, server-side, so the terminal and the web list cannot drift.
125
+ if (criteria.query)
126
+ params.addFilter('search', criteria.query);
123
127
  if (criteria.state)
124
128
  params.addFilter('state', criteria.state);
125
129
  // '1'/'0', never the boolean: the backend's integer-backed boolean storage
@@ -127,16 +131,16 @@ function applyTicketCriteria(params, criteria, options) {
127
131
  if (criteria.closed !== null)
128
132
  params.addFilter('closed', criteria.closed ? '1' : '0');
129
133
  // Direct children only; top level, so it ANDs with project/state while
130
- // the search OR group stays untouched (AC-5).
134
+ // the search stays untouched (AC-5).
131
135
  if (criteria.parent)
132
136
  params.addFilter('parent_id.id', criteria.parent.id);
133
137
  // Assigned to this user only; a top-level AND like parent, never part of
134
- // the search OR group (GAIA-299 AC-4). Skipped when the builder itself is
138
+ // the search (GAIA-299 AC-4). Skipped when the builder itself is
135
139
  // already scoped to an assignee (user ticket list).
136
140
  if (criteria.who)
137
141
  params.addFilter('assignee_user_id.id', criteria.who.id);
138
142
  // Owned by (created by) this user only; a top-level AND like who/parent,
139
- // never part of the search OR group (GAIA-416). `owner_user_id` is the
143
+ // never part of the search (GAIA-416). `owner_user_id` is the
140
144
  // entity's owner key — see `ticket-detail/index.ts` — and is unrelated to
141
145
  // the web-UI's `owner` exposed-Views facet.
142
146
  if (criteria.owner)
@@ -11,7 +11,7 @@ const ticketKey = (id) => dataKey(`ticket/${id}`);
11
11
  * requests but this list omits is always "missing", so the store retries a
12
12
  * fetch for it on every `enter()` even though the supplied document already
13
13
  * has nothing more to give. That retry does not merely waste a round trip:
14
- * `TicketContentController.enter()`'s `watchTicket` subscription and the
14
+ * `TicketContentScreen.enter()`'s `watchTicket` subscription and the
15
15
  * retry's own `start()` both react to the same store write and race to bump
16
16
  * `this.revision`, and whichever loses leaves `AsyncScreen.busy` stuck TRUE
17
17
  * forever — measured as `renderPipe`'s `settledActive` spinning until the
@@ -330,7 +330,7 @@ export type TicketPage = Page<TicketRow>;
330
330
  * screen happens to be holding. `null` when the server reports no count, which
331
331
  * `readTotal` already treats as the explicit unknown rather than as a zero.
332
332
  *
333
- * It builds the SAME query as {@link loadFilteredTickets} — the same OR group over
333
+ * It builds the SAME query as {@link loadFilteredTickets} — the same `filter[search]` over
334
334
  * identifier/title, the same project/state/parent/who ANDs — with exactly one
335
335
  * criterion removed. That is what makes the number it returns an answer to "how
336
336
  * many is `closed` holding back" and not to some other question; a second,
@@ -298,7 +298,7 @@ export function mapTicketDocument(document) {
298
298
  * screen happens to be holding. `null` when the server reports no count, which
299
299
  * `readTotal` already treats as the explicit unknown rather than as a zero.
300
300
  *
301
- * It builds the SAME query as {@link loadFilteredTickets} — the same OR group over
301
+ * It builds the SAME query as {@link loadFilteredTickets} — the same `filter[search]` over
302
302
  * identifier/title, the same project/state/parent/who ANDs — with exactly one
303
303
  * criterion removed. That is what makes the number it returns an answer to "how
304
304
  * many is `closed` holding back" and not to some other question; a second,
@@ -112,6 +112,18 @@ export declare const EDIT_FORM_TERMINAL: {
112
112
  apply(s: FormState<EditValues>, options: EditFormOptions, intent: import("../../../Form/form.js").Intent): FormState<EditValues>;
113
113
  toObject: (s: FormState<EditValues>, options: EditFormOptions) => EditFormPayload;
114
114
  };
115
+ /**
116
+ * A closed ticket's popup: read-only, no Save and no Cancel ticket. The server
117
+ * freezes a ticket on its stored `closed` flag and answers any PATCH with 403.
118
+ */
119
+ export declare const EDIT_FORM_CLOSED: {
120
+ fields: import("../../../Form/field.js").FieldDecl[];
121
+ activeField: (s: FormState<EditValues>) => import("../../../Form/field.js").FieldDecl;
122
+ move: (s: FormState<EditValues>, d: number) => FormState<EditValues>;
123
+ initial(options: EditFormOptions, prefill?: Partial<EditValues>): FormState<EditValues>;
124
+ apply(s: FormState<EditValues>, options: EditFormOptions, intent: import("../../../Form/form.js").Intent): FormState<EditValues>;
125
+ toObject: (s: FormState<EditValues>, options: EditFormOptions) => EditFormPayload;
126
+ };
115
127
  export type EditFormDeclaration = typeof EDIT_FORM;
116
128
  /** The ticket's state as the popup sees it; `''` when the read carried none. */
117
129
  export declare const ticketState: (document: TicketDetailDocument) => string;
@@ -127,6 +139,7 @@ export declare const editFormFor: (document: TicketDetailDocument) => EditFormDe
127
139
  /** Field order in the popup, per declaration. */
128
140
  export declare const EDIT_FIELDS: FieldDef<EditFieldId>[];
129
141
  export declare const EDIT_FIELDS_TERMINAL: FieldDef<EditFieldId>[];
142
+ export declare const EDIT_FIELDS_CLOSED: FieldDef<EditFieldId>[];
130
143
  export type EditFormState = FormState<EditValues>;
131
144
  /**
132
145
  * No lists to OFFER — bags start empty until the adapter / option loader fills
@@ -102,7 +102,7 @@ const timeAttributes = (v) => {
102
102
  * every index below it; absence has to be a property of WHICH DECLARATION is in
103
103
  * force.
104
104
  */
105
- const declareEditForm = (cancellable) => declareForm({
105
+ const declareEditForm = (savable, cancellable) => declareForm({
106
106
  fields: (f) => [
107
107
  f.select('assignee', 'Assignee', { from: 'users', clearable: true }),
108
108
  f.select('conductor', 'Conductor', {
@@ -119,7 +119,7 @@ const declareEditForm = (cancellable) => declareForm({
119
119
  pageSize: PARENT_PAGE_SIZE,
120
120
  }),
121
121
  f.multi('labels', 'Labels', { from: 'labels' }),
122
- f.action('Save', 'submit'),
122
+ ...(savable ? [f.action('Save', 'submit')] : []),
123
123
  ...(cancellable
124
124
  ? [f.action('Cancel ticket', CANCEL_TICKET_ACTION_ID)]
125
125
  : []),
@@ -136,9 +136,14 @@ const declareEditForm = (cancellable) => declareForm({
136
136
  }),
137
137
  });
138
138
  /** The ordinary popup: a non-terminal ticket can be cancelled from it. */
139
- export const EDIT_FORM = declareEditForm(true);
139
+ export const EDIT_FORM = declareEditForm(true, true);
140
140
  /** A terminal ticket's popup: assignment, times, structure — no Cancel ticket. */
141
- export const EDIT_FORM_TERMINAL = declareEditForm(false);
141
+ export const EDIT_FORM_TERMINAL = declareEditForm(true, false);
142
+ /**
143
+ * A closed ticket's popup: read-only, no Save and no Cancel ticket. The server
144
+ * freezes a ticket on its stored `closed` flag and answers any PATCH with 403.
145
+ */
146
+ export const EDIT_FORM_CLOSED = declareEditForm(false, false);
142
147
  /** The ticket's state as the popup sees it; `''` when the read carried none. */
143
148
  export const ticketState = (document) => {
144
149
  const value = document.data?.attributes?.state;
@@ -152,12 +157,17 @@ export const ticketState = (document) => {
152
157
  * Cancel ticket on a thin read would cost a legitimate affordance; offering it
153
158
  * costs at worst the server's 422, which the popup already renders in full.
154
159
  */
155
- export const editFormFor = (document) => TERMINAL_TICKET_STATES.has(ticketState(document))
156
- ? EDIT_FORM_TERMINAL
157
- : EDIT_FORM;
160
+ export const editFormFor = (document) => {
161
+ if (document.data?.attributes?.closed === true)
162
+ return EDIT_FORM_CLOSED;
163
+ return TERMINAL_TICKET_STATES.has(ticketState(document))
164
+ ? EDIT_FORM_TERMINAL
165
+ : EDIT_FORM;
166
+ };
158
167
  /** Field order in the popup, per declaration. */
159
168
  export const EDIT_FIELDS = EDIT_FORM.fields;
160
169
  export const EDIT_FIELDS_TERMINAL = EDIT_FORM_TERMINAL.fields;
170
+ export const EDIT_FIELDS_CLOSED = EDIT_FORM_CLOSED.fields;
161
171
  /**
162
172
  * No lists to OFFER — bags start empty until the adapter / option loader fills
163
173
  * them. Prefill names every value, and `clear`/`toObject` read no option list
@@ -1,4 +1,4 @@
1
- import type { FullScreenChrome } from '../../../Screen/full-screen.js';
1
+ import type { FullScreenChrome, ListFilterBadge } from '../../../Screen/full-screen.js';
2
2
  import { ListScreen } from '../../../Screen/list-screen.js';
3
3
  import type { ScreenContext } from '../../../Screen/screen.js';
4
4
  import { type Page } from '../../collection.js';
@@ -69,16 +69,22 @@ export declare class ConductorTicketsScreen extends ListScreen<TicketRow, void>
69
69
  }];
70
70
  readonly emptyMessage: "No ticket is assigned to this conductor.";
71
71
  };
72
+ searchQuery: string;
73
+ private readonly search;
74
+ reloadSearch(context: ScreenContext): Promise<boolean>;
72
75
  protected tableContext(): void;
73
76
  protected loadRows(context: ScreenContext, page: number): Promise<Page<TicketRow>>;
74
77
  protected renderLoading(): string[];
75
78
  protected chrome(): FullScreenChrome;
79
+ protected badges(): readonly ListFilterBadge[];
80
+ protected notice(): string | undefined;
76
81
  protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
77
82
  activate(index: number, context: ScreenContext): boolean;
78
83
  select(index: number, context: ScreenContext): boolean;
79
84
  handleInput(input: string, context: ScreenContext): boolean;
80
85
  private headerDetail;
81
86
  protected didLoad(_data: Page<TicketRow>, context: ScreenContext): void;
87
+ leave(): void;
82
88
  reset(): void;
83
89
  /** The pipe's own text — the same table + rows the drawn tree uses. */
84
90
  renderNonInteractive(): string[];
@@ -7,6 +7,7 @@ import { conductorTitle } from '../../conductor/Screen/conductor-overview.js';
7
7
  import { LIST_PAGE_SIZE } from '../../cycle.js';
8
8
  import { buildConductorTicketQueryParams } from '../Data/query.js';
9
9
  import { CONDUCTOR_TICKET_TABLE, mapTicketData, } from '../Data/tickets.js';
10
+ import { TicketSearch } from './ticket-search.js';
10
11
  /** Assigned — the open tickets held by one conductor. */
11
12
  export class ConductorTicketsScreen extends ListScreen {
12
13
  static route = {
@@ -19,6 +20,13 @@ export class ConductorTicketsScreen extends ListScreen {
19
20
  id = ConductorTicketsScreen.route.id;
20
21
  frameId = 'conductor';
21
22
  table = CONDUCTOR_TICKET_TABLE;
23
+ searchQuery = '';
24
+ search = new TicketSearch(this);
25
+ reloadSearch(context) {
26
+ this.page = 0;
27
+ this.selectedIndex = 0;
28
+ return this.reloadInPlace(context);
29
+ }
22
30
  tableContext() {
23
31
  // No clock/context needed for this table.
24
32
  }
@@ -27,10 +35,7 @@ export class ConductorTicketsScreen extends ListScreen {
27
35
  throw new Error('Assigned requires services');
28
36
  const conductorId = context.params?.conductorId ?? '';
29
37
  const window = { offset: page * LIST_PAGE_SIZE, limit: LIST_PAGE_SIZE };
30
- const params = buildConductorTicketQueryParams(conductorId, {
31
- size: window.limit,
32
- index: page,
33
- });
38
+ const params = buildConductorTicketQueryParams(conductorId, { size: window.limit, index: page }, this.searchQuery);
34
39
  return loadPage(context.services.client, 'gaia_ticket/gaia_ticket', params, (data, included) => {
35
40
  const index = new Map(included.filter((r) => r.id).map((r) => [r.id, r]));
36
41
  return data.map((item) => mapTicketData(item, index));
@@ -45,11 +50,22 @@ export class ConductorTicketsScreen extends ListScreen {
45
50
  selectedIndex: this.selectedIndex,
46
51
  rowIds: this.rowNodeIds,
47
52
  title: conductorTitle(this.headerDetail),
48
- meta: [this.countLabel(), ...this.pagingSegments()]
53
+ meta: [
54
+ [this.countLabel(), ...this.pagingSegments()]
55
+ .filter(Boolean)
56
+ .join(' · '),
57
+ this.search.hint(),
58
+ ]
49
59
  .filter(Boolean)
50
- .join(' · '),
60
+ .join(' · '),
51
61
  };
52
62
  }
63
+ badges() {
64
+ return [this.search.badge()];
65
+ }
66
+ notice() {
67
+ return this.search.status || undefined;
68
+ }
53
69
  actions() {
54
70
  return [
55
71
  ...(this.rows.length > 0
@@ -76,6 +92,8 @@ export class ConductorTicketsScreen extends ListScreen {
76
92
  handleInput(input, context) {
77
93
  if (super.handleInput(input, context))
78
94
  return true;
95
+ if (this.search.handleKey(input, context))
96
+ return true;
79
97
  if (this.handlePagingInput(input, context))
80
98
  return true;
81
99
  if (this.handleCursorInput(input, context))
@@ -94,8 +112,14 @@ export class ConductorTicketsScreen extends ListScreen {
94
112
  context.requestDraw();
95
113
  });
96
114
  }
115
+ leave() {
116
+ this.search.close();
117
+ super.leave();
118
+ }
97
119
  reset() {
98
120
  this.resetList();
121
+ this.searchQuery = '';
122
+ this.search.reset();
99
123
  this.headerDetail = undefined;
100
124
  super.reset();
101
125
  }
@@ -33,7 +33,7 @@ export declare class EditAssignmentFormScreen extends AsyncModalScreen<EditAssig
33
33
  * Chosen once, in `didLoad`, and used for BOTH the rendered rows and the key
34
34
  * routing — the kernel indexes its field array by `state.active`, so two
35
35
  * sources for that array is two ways to disagree about which row the `▸`
36
- * marker is on. Defaults to the terminal (no-cancel) declaration: a screen
36
+ * marker is on. Defaults to the closed (read-only) declaration: a screen
37
37
  * that somehow renders before its document has arrived offers less rather
38
38
  * than more.
39
39
  */
@@ -86,6 +86,8 @@ export declare class EditAssignmentFormScreen extends AsyncModalScreen<EditAssig
86
86
  leave(): void;
87
87
  reset(): void;
88
88
  protected modalChrome(context: ScreenContext): ModalChrome;
89
+ /** Whether this ticket's popup can write at all; a closed one cannot. */
90
+ private get savable();
89
91
  protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
90
92
  }
91
93
  export {};
@@ -9,7 +9,7 @@ import { activeRunInfo } from '../../run/Data/active-run.js';
9
9
  import { resolveWhoCandidate } from '../../user/Data/users.js';
10
10
  import { EDIT_FORM_REGION, editFormTree, } from '../Component/edit-form/index.js';
11
11
  import { loadTicketDetail } from '../Data/detail.js';
12
- import { CANCEL_TICKET_ACTION_ID, cancelEditPayload, EDIT_FORM_TERMINAL, EMPTY_EDIT_FORM_OPTIONS, editFormFor, initialEditForm, serializeEdit, } from '../Form/edit.js';
12
+ import { CANCEL_TICKET_ACTION_ID, cancelEditPayload, EDIT_FORM_CLOSED, EMPTY_EDIT_FORM_OPTIONS, editFormFor, initialEditForm, serializeEdit, } from '../Form/edit.js';
13
13
  import { loadEditFormOptions } from '../Form/edit-data.js';
14
14
  import { editParentOptionSource } from '../Form/edit-parent-source.js';
15
15
  import { keyToIntent } from '../Form/keys.js';
@@ -44,11 +44,11 @@ export class EditAssignmentFormScreen extends AsyncModalScreen {
44
44
  * Chosen once, in `didLoad`, and used for BOTH the rendered rows and the key
45
45
  * routing — the kernel indexes its field array by `state.active`, so two
46
46
  * sources for that array is two ways to disagree about which row the `▸`
47
- * marker is on. Defaults to the terminal (no-cancel) declaration: a screen
47
+ * marker is on. Defaults to the closed (read-only) declaration: a screen
48
48
  * that somehow renders before its document has arrived offers less rather
49
49
  * than more.
50
50
  */
51
- declaration = EDIT_FORM_TERMINAL;
51
+ declaration = EDIT_FORM_CLOSED;
52
52
  form;
53
53
  action;
54
54
  message = '';
@@ -223,12 +223,19 @@ export class EditAssignmentFormScreen extends AsyncModalScreen {
223
223
  return true;
224
224
  if (intent.kind === 'cancel')
225
225
  this.action = { type: 'back' };
226
+ else if (intent.kind === 'submit' && !this.savable) {
227
+ // A closed ticket's popup is read-only: Ctrl+S writes nothing.
228
+ this.message = 'This ticket is closed and read-only.';
229
+ }
226
230
  else if (intent.kind === 'submit') {
227
231
  // D10: Ctrl+S ALWAYS Save, even when the cursor is on Cancel ticket.
228
232
  // Only Enter/submit while standing on the cancelTicket action cancels.
229
233
  const cancelTicket = input !== 'ctrl+s' && field.id === CANCEL_TICKET_ACTION_ID;
230
234
  this.submit(context, data, cancelTicket);
231
235
  }
236
+ else if (!this.savable && intent.kind !== 'move') {
237
+ // A closed ticket's values stay as they are; only ↑↓ moves.
238
+ }
232
239
  else {
233
240
  this.form = this.declaration.apply(this.form, data.options, intent);
234
241
  if (field.query !== undefined &&
@@ -258,7 +265,7 @@ export class EditAssignmentFormScreen extends AsyncModalScreen {
258
265
  super.leave();
259
266
  }
260
267
  reset() {
261
- this.declaration = EDIT_FORM_TERMINAL;
268
+ this.declaration = EDIT_FORM_CLOSED;
262
269
  this.form = undefined;
263
270
  this.action = undefined;
264
271
  this.message = '';
@@ -279,12 +286,20 @@ export class EditAssignmentFormScreen extends AsyncModalScreen {
279
286
  ''),
280
287
  };
281
288
  }
289
+ /** Whether this ticket's popup can write at all; a closed one cannot. */
290
+ get savable() {
291
+ return this.declaration.fields.some((f) => f.id === 'submit');
292
+ }
282
293
  actions() {
283
294
  // GAIA-360 D12 — Esc:close (not cancel); Cancel ticket is lifecycle.
284
295
  return [
285
296
  actionItem('edit-field', '↑↓', 'field'),
286
- actionItem('edit-change', '←→', 'change'),
287
- actionItem('edit-save', 'Ctrl+S', 'save'),
297
+ ...(this.savable
298
+ ? [
299
+ actionItem('edit-change', '←→', 'change'),
300
+ actionItem('edit-save', 'Ctrl+S', 'save'),
301
+ ]
302
+ : []),
288
303
  actionItem('edit-esc', 'Esc', 'close'),
289
304
  ];
290
305
  }
@@ -24,7 +24,12 @@ export declare class MyWorkScreen extends AsyncScreen<MyWorkResult> {
24
24
  private readonly cursor;
25
25
  private currentCriteria;
26
26
  private readonly filter;
27
- private get rows();
27
+ private readonly search;
28
+ get rows(): readonly TicketRow[];
29
+ get searchQuery(): string;
30
+ set searchQuery(query: string);
31
+ showRows(rows: readonly TicketRow[]): void;
32
+ reloadSearch(context: ScreenContext): Promise<boolean>;
28
33
  reload(context: ScreenContext): void;
29
34
  protected load(context: ScreenContext): Promise<MyWorkResult>;
30
35
  protected renderLoading(): string[];
@@ -38,9 +43,11 @@ export declare class MyWorkScreen extends AsyncScreen<MyWorkResult> {
38
43
  * parent · project · closed · clear).
39
44
  */
40
45
  protected badges(): readonly ListFilterBadge[];
46
+ protected notice(): string | undefined;
41
47
  protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
42
48
  activate(index: number, context: ScreenContext): boolean;
43
49
  handleInput(input: string, context: ScreenContext): boolean;
50
+ leave(): void;
44
51
  reset(): void;
45
52
  get criteria(): TicketCriteria;
46
53
  /** Reload through `retry`, mirroring `TicketsScreen.setCriteria`. */
@@ -6,6 +6,7 @@ import { resolveWhoCandidate } from '../../user/Data/users.js';
6
6
  import { DEFAULT_TICKET_CRITERIA, describeCriteria, } from '../Data/query.js';
7
7
  import { loadFilteredUserTickets, TICKET_TABLE, } from '../Data/tickets.js';
8
8
  import { TicketFilterBar } from './ticket-filter-bar.js';
9
+ import { TicketSearch } from './ticket-search.js';
9
10
  const MY_WORK_TABLE = {
10
11
  ...TICKET_TABLE,
11
12
  emptyMessage: 'No open tickets.',
@@ -23,13 +24,33 @@ export class MyWorkScreen extends AsyncScreen {
23
24
  // GAIA-540: a single bar shared with the project ticket list, disabled for
24
25
  // `who` (My work = my tickets, the criterion is implicit) and `mine` (the
25
26
  // shortcut makes no sense when the assignee is fixed). The bar still
26
- // dispatches `s`/`x`/`p`/`r`/`/`/`c`, and the bar's `applyTo` mirrors the
27
- // project list's guard so a cached re-entry cannot surface a non-matching
28
- // row.
27
+ // dispatches `s`/`x`/`p`/`r`/`c`. The bar's `applyTo` still guards
28
+ // state/closed/parent/project on a cached re-entry; the search line is
29
+ // the server's.
29
30
  filter = new TicketFilterBar(this, new Set(['who', 'mine']));
31
+ search = new TicketSearch(this);
30
32
  get rows() {
31
33
  return this.readyValue?.page.rows ?? [];
32
34
  }
35
+ get searchQuery() {
36
+ return this.currentCriteria.query;
37
+ }
38
+ set searchQuery(query) {
39
+ this.currentCriteria = { ...this.currentCriteria, query };
40
+ }
41
+ showRows(rows) {
42
+ const current = this.readyValue;
43
+ if (!current)
44
+ return;
45
+ this.replaceReady({
46
+ ...current,
47
+ page: { ...current.page, rows: [...rows], total: null, window: null },
48
+ });
49
+ }
50
+ reloadSearch(context) {
51
+ this.cursor.reset();
52
+ return this.reloadInPlace(context);
53
+ }
33
54
  reload(context) {
34
55
  this.retry(context);
35
56
  }
@@ -77,6 +98,7 @@ export class MyWorkScreen extends AsyncScreen {
77
98
  `${this.rows.length} open ticket(s)`,
78
99
  describe,
79
100
  context.services?.workspace ?? '',
101
+ this.search.hint(),
80
102
  ]
81
103
  .filter(Boolean)
82
104
  .join(' · ');
@@ -97,6 +119,9 @@ export class MyWorkScreen extends AsyncScreen {
97
119
  badges() {
98
120
  return this.filter.badges();
99
121
  }
122
+ notice() {
123
+ return this.search.status || undefined;
124
+ }
100
125
  actions() {
101
126
  return [
102
127
  actionItem('my-work-open', 'Enter', 'open'),
@@ -113,7 +138,10 @@ export class MyWorkScreen extends AsyncScreen {
113
138
  handleInput(input, context) {
114
139
  if (super.handleInput(input, context))
115
140
  return true;
116
- // The bar first: its key dispatch covers `s`/`x`/`p`/`r`/`/`/`c`. The
141
+ // The open search field takes every key, so it goes before the bar.
142
+ if (this.search.handleKey(input, context))
143
+ return true;
144
+ // The bar next: its key dispatch covers `s`/`x`/`p`/`r`/`c`. The
117
145
  // cursor/enter branches stay below so the bar claims only the keys it
118
146
  // names.
119
147
  if (this.filter.handleKey(input, context))
@@ -126,7 +154,12 @@ export class MyWorkScreen extends AsyncScreen {
126
154
  return this.activate(this.cursor.index, context);
127
155
  return false;
128
156
  }
157
+ leave() {
158
+ this.search.close();
159
+ super.leave();
160
+ }
129
161
  reset() {
162
+ this.search.reset();
130
163
  this.cursor.reset();
131
164
  this.currentCriteria = { ...DEFAULT_TICKET_CRITERIA };
132
165
  super.reset();
@@ -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 comments view and its interaction state. */
5
- declare class TicketCommentsController extends TicketContentController {
6
- readonly id: string;
7
- readonly tab = "comments";
8
- protected readonly includes: string[];
9
- protected renderLoading(): string[];
10
- protected fullNonInteractive(document: TicketDetailDocument, context: ScreenContext): string[];
11
- }
12
- /** Public History route: ReactScreen wrapper around the comments controller. */
3
+ import { TicketContentScreen } from './ticket-content-screen.js';
4
+ /** Public History route: the ticket's comments view and its interaction state. */
13
5
  export declare class TicketCommentsScreen extends TicketContentScreen {
14
6
  static readonly route: {
15
7
  id: string;
@@ -27,6 +19,8 @@ export declare class TicketCommentsScreen 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: TicketCommentsController;
22
+ readonly tab = "comments";
23
+ protected readonly includes: string[];
24
+ protected renderLoading(): string[];
25
+ protected fullNonInteractive(document: TicketDetailDocument, context: ScreenContext): string[];
31
26
  }
32
- export {};
@@ -1,22 +1,9 @@
1
1
  import { flattenSections } from '../../../Component/section-list/index.js';
2
2
  import { detailSections } 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
  const INCLUDES = ['comments'];
6
- /** Owns the ticket's comments view and its interaction state. */
7
- class TicketCommentsController extends TicketContentController {
8
- id = TicketCommentsScreen.route.id;
9
- tab = 'comments';
10
- includes = INCLUDES;
11
- renderLoading() {
12
- return ['Loading comments…'];
13
- }
14
- fullNonInteractive(document, context) {
15
- const nowSeconds = context.nowSeconds?.() ?? Math.floor(Date.now() / 1000);
16
- return flattenSections(detailSections(document, this.labels, nowSeconds).filter((section) => section.heading === 'Comments')).lines;
17
- }
18
- }
19
- /** Public History route: ReactScreen wrapper around the comments controller. */
6
+ /** Public History route: the ticket's comments view and its interaction state. */
20
7
  export class TicketCommentsScreen extends TicketContentScreen {
21
8
  static route = {
22
9
  id: 'ticket.comments',
@@ -27,5 +14,13 @@ export class TicketCommentsScreen extends TicketContentScreen {
27
14
  context: ticketInputContext,
28
15
  };
29
16
  id = TicketCommentsScreen.route.id;
30
- controller = new TicketCommentsController();
17
+ tab = 'comments';
18
+ includes = INCLUDES;
19
+ renderLoading() {
20
+ return ['Loading comments…'];
21
+ }
22
+ fullNonInteractive(document, context) {
23
+ const nowSeconds = context.nowSeconds?.() ?? Math.floor(Date.now() / 1000);
24
+ return flattenSections(detailSections(document, this.labels, nowSeconds).filter((section) => section.heading === 'Comments')).lines;
25
+ }
31
26
  }