@bud.sh/app 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +41 -31
  2. package/dist/client/assets/bud/design.js +1 -1
  3. package/dist/client/assets/canvas-Bih9EdKJ.js +1 -0
  4. package/dist/client/assets/difference-worker-Bzg-rizy.js +8 -0
  5. package/dist/client/assets/dist-CdE5wBkg.js +5 -0
  6. package/dist/client/assets/files-1dLuoM2f.js +1 -0
  7. package/dist/client/assets/i18n-CkO6VFI0.js +1 -0
  8. package/dist/client/assets/index-Cn2mxroU.css +2 -0
  9. package/dist/client/assets/{index-ChANGtl-.js → index-DtISWZp9.js} +2 -2
  10. package/dist/client/assets/pages-BJ-tNJ4J.js +1 -0
  11. package/dist/client/assets/{project-branches-DO897zM6.js → project-branches-Ce3rXrkc.js} +1 -1
  12. package/dist/client/assets/project-comments-CPUONfs8.js +1 -0
  13. package/dist/client/assets/project-thread-links-C-TGkq59.js +1 -0
  14. package/dist/client/assets/{project-threads-Lay351p4.js → project-threads-Bv7IDBgi.js} +105 -105
  15. package/dist/client/assets/project-work-DAE9-olg.js +1 -0
  16. package/dist/client/assets/{task-design-Avb2RO4w.js → task-design-CwySIAOl.js} +3 -3
  17. package/dist/client/assets/tasks-KWsCLi_h.js +3 -0
  18. package/dist/client/assets/wasm-inline-DSUyFn1L.js +1 -0
  19. package/dist/client/assets/workspace-CVdcfaaB.js +2 -0
  20. package/dist/client/index.html +3 -3
  21. package/dist/hosted/assets/{canvas-V36732ER.js → canvas-AII16n9i.js} +97 -6
  22. package/dist/hosted/assets/{comment-data-CF2ZV_3k.js → comment-data-BXfMElfx.js} +2004 -402
  23. package/dist/hosted/assets/dist-BCSTeNf4.js +4785 -0
  24. package/dist/hosted/assets/{files-DjSCjfU7.js → files-D_1yeAVO.js} +10 -5
  25. package/dist/hosted/assets/{pages-BzjzeuPu.js → pages-CN_SbKWA.js} +3 -3
  26. package/dist/hosted/assets/{project-branches-BT97J9i7.js → project-branches-Bxp0DCwg.js} +1 -1
  27. package/dist/hosted/assets/{project-thread-links-BRLgByEe.js → project-thread-links-KqQoKBLE.js} +76 -4
  28. package/dist/{node-host/assets/screen-CZXWJEds.js → hosted/assets/screen-DpAcDUKR.js} +29 -1
  29. package/dist/hosted/assets/tasks-D66LlwxM.js +1423 -0
  30. package/dist/hosted/assets/wasm-inline-Dl9tqabx.js +4 -0
  31. package/dist/hosted/hosted.js +7885 -6219
  32. package/dist/node/assets/Stream-CAm1eJ2n.js +16604 -0
  33. package/dist/{server/assets/bridge-B_Blt55E.js → node/assets/bridge-DQSBYHmw.js} +124 -290
  34. package/dist/node/assets/config-9_jWwNBh.js +1067 -0
  35. package/dist/node/assets/load-config.server-CMVtRVmO.js +261 -0
  36. package/dist/node/assets/module-3GgtT4Xs.js +308 -0
  37. package/dist/node/assets/server-DTQFuoX2.js +846 -0
  38. package/dist/node/assets/src-D-kG1GxB.js +2 -0
  39. package/dist/node/assets/{src-CerrKjID.js → src-Dwe9APUx.js} +93 -3
  40. package/dist/node/assets/src-DysjfbM8.js +8592 -0
  41. package/dist/node/bridge.js +1 -845
  42. package/dist/node/cli.js +206 -0
  43. package/dist/node/design.js +811 -13
  44. package/dist/node/index.js +2 -739
  45. package/dist/node/plugins.js +5711 -0
  46. package/dist/node/server.js +1 -6746
  47. package/dist/node/vite.js +121 -0
  48. package/dist/{server/assets/canvas-Dc1mYCwQ.js → node-host/assets/canvas-BeDoufy1.js} +97 -7
  49. package/dist/node-host/assets/{comment-data-DyIZNpfG.js → comment-data-DE2T9yTj.js} +1980 -376
  50. package/dist/node-host/assets/dist-BCSTeNf4.js +4785 -0
  51. package/dist/node-host/assets/{files-Dh2TiQZq.js → files-Dl-PTS09.js} +11 -6
  52. package/dist/node-host/assets/{pages-BxpjbYhs.js → pages-DL2sk6_U.js} +3 -3
  53. package/dist/node-host/assets/{project-branches-B7HUGiVK.js → project-branches-DGp5ELct.js} +1 -1
  54. package/dist/{server/assets/project-thread-links-CBOpf6zk.js → node-host/assets/project-thread-links-CgBV3Jcm.js} +76 -5
  55. package/dist/{hosted/assets/screen-CKp2C7kx.js → node-host/assets/screen-B55yyVfb.js} +29 -1
  56. package/dist/node-host/assets/tasks-DZRNkoxe.js +1425 -0
  57. package/dist/node-host/assets/wasm-inline-Dl9tqabx.js +4 -0
  58. package/dist/node-host/node-host.js +6127 -4438
  59. package/dist/server/assets/{branch-previews-ClkoJ0lM.js → branch-previews-DedrpnVR.js} +1555 -673
  60. package/dist/{node-host/assets/canvas-TRUfxfL5.js → server/assets/canvas-CJWQ-jjv.js} +98 -6
  61. package/dist/server/assets/{comment-data-CsjukwAW.js → comment-data-Rg42ToTW.js} +1666 -47
  62. package/dist/server/assets/dist-BCSTeNf4.js +4785 -0
  63. package/dist/server/assets/{events-iHpLGr3k.js → events-BH_rAu7O.js} +155 -20
  64. package/dist/server/assets/{files-E9mSFeoc.js → files-CPJqKeMo.js} +12 -7
  65. package/dist/server/assets/{listener-DN5TyOjJ.js → listener-D61O6PzF.js} +708 -12
  66. package/dist/server/assets/{pages-BBBXyJWV.js → pages-KJY_Ba92.js} +4 -4
  67. package/dist/server/assets/{project-branches-B0YIK6Ur.js → project-branches-qG9ACZvo.js} +2 -2
  68. package/dist/{node-host/assets/project-thread-links-CauJF0Cz.js → server/assets/project-thread-links-CqK0lqCF.js} +77 -4
  69. package/dist/{node-host/assets/tasks-request-ZWmbc8fE.js → server/assets/project-work-DoIwxWMm.js} +87 -2
  70. package/dist/server/assets/run-BJ8r5jSh.js +572 -0
  71. package/dist/server/assets/{screen-BIQA2mr4.js → screen-CBrHPFug.js} +30 -2
  72. package/dist/server/assets/{server-DxK-UB_Z.js → server-Dkbp5stG.js} +1521 -793
  73. package/dist/server/assets/{services-BRrgXYlT.js → services-BTfig8A8.js} +234 -997
  74. package/dist/server/assets/tasks-Bgu5Lg9Y.js +1427 -0
  75. package/dist/server/assets/wasm-inline-Dl9tqabx.js +4 -0
  76. package/dist/server/capture.js +1 -2
  77. package/dist/server/migration.js +569 -12
  78. package/dist/server/runtime.js +7 -5
  79. package/dist/server/server.js +3 -3
  80. package/dist/types/assets/{config-DIC2cf_S.d.ts → config-CvcYmE2s.d.ts} +188 -50
  81. package/dist/types/assets/{contract-CAT-XFNq.d.ts → contract-B7yuuqSn.d.ts} +44 -2
  82. package/dist/types/assets/{items-CaVkByHk.d.ts → items-BQdaVksf.d.ts} +12 -1
  83. package/dist/types/assets/memory-state-BBW-FAVA.d.ts +236 -0
  84. package/dist/types/assets/picker-Be5Gx4WR.d.ts +9 -0
  85. package/dist/types/assets/{task-design-xJOhRNWh.d.ts → task-design-BDHKFHpp.d.ts} +3 -1
  86. package/dist/types/bridge.d.ts +1 -1
  87. package/dist/types/companion.d.ts +3 -3
  88. package/dist/types/design.d.ts +2 -2
  89. package/dist/types/hosted.d.ts +3 -3
  90. package/dist/types/index.d.ts +2 -2
  91. package/dist/types/node-host.d.ts +2 -2
  92. package/dist/types/plugins.d.ts +445 -0
  93. package/dist/types/screen.d.ts +2 -2
  94. package/dist/types/server.d.ts +2 -228
  95. package/dist/types/vite.d.ts +16 -0
  96. package/package.json +19 -3
  97. package/dist/client/assets/canvas-Cu6R6xxT.js +0 -1
  98. package/dist/client/assets/difference-worker-jfTSn-Tw.js +0 -8
  99. package/dist/client/assets/files-js6ElfkU.js +0 -1
  100. package/dist/client/assets/i18n-DxqFr2mi.js +0 -1
  101. package/dist/client/assets/index-CFFxP0gS.css +0 -2
  102. package/dist/client/assets/pages-6_lhiQmz.js +0 -1
  103. package/dist/client/assets/project-comments-DFFKVpia.js +0 -1
  104. package/dist/client/assets/project-thread-links-NLFWQRD2.js +0 -1
  105. package/dist/client/assets/project-work-du0Y-L5Y.js +0 -1
  106. package/dist/client/assets/tasks-CA-E8bbK.js +0 -1
  107. package/dist/client/assets/tasks-request-C55IeuiP.js +0 -1
  108. package/dist/client/assets/workspace-Cw5HvX5d.js +0 -2
  109. package/dist/hosted/assets/tasks-Dj7AyBJB.js +0 -649
  110. package/dist/hosted/assets/tasks-request-DZob623r.js +0 -113
  111. package/dist/node-host/assets/tasks-Bs1IOpeU.js +0 -651
  112. package/dist/server/assets/project-work-qSKFO5yj.js +0 -88
  113. package/dist/server/assets/tasks-CiXodsLN.js +0 -652
  114. package/dist/server/assets/tasks-request-DhwCzpRi.js +0 -115
  115. package/dist/types/assets/picker-D1Bw3ndx.d.ts +0 -14
@@ -1,651 +0,0 @@
1
- import { $t as Button, B as TaskWorkspace, C as ThreadTaskLinks, Dt as taskHref, En as predictWorkUpdate, Et as selectedTask, Jt as CommentThreadDetail, Kr as parseWorkMutationResult, Ot as taskRole, Rr as parseSectionData, S as writeRows, T as ItemDiscussion, Tn as predictWorkCreate, Tt as readConversationLocation, Ur as parseWorkDetail, Xr as parseWorkSyncResult, Xt as CommentComposer, Yt as CommentThreadItem, b as plainRow, cn as toast, gn as Text, h as storedWorkspace, l as projectStore, mn as t, nt as CommentThreadHeading, qt as taskRoutes, wn as predictWorkAction, x as writeOptimistically, xt as conversationLabel, y as routes } from "./comment-data-DyIZNpfG.js";
2
- import { n as requestTasks, t as TaskRequestError } from "./tasks-request-ZWmbc8fE.js";
3
- import { r as projectWork } from "../node-host.js";
4
- import { i as loadItemComments, n as createProjectComments, t as defineScreen } from "./screen-CZXWJEds.js";
5
- import { n as ProjectTaskPicker, t as ProjectThreadLinks } from "./project-thread-links-CauJF0Cz.js";
6
- import { on, ref } from "remix/component";
7
- import { Fragment, jsx, jsxs } from "remix/component/jsx-runtime";
8
- //#region app/client/task-discussion.tsx
9
- /**
10
- * Owns a task's filtered discussions and lazy detail requests within its mounted lifetime.
11
- * @param handle - The trusted project capability and currently displayed work-item ID.
12
- * @returns The discussion block supplied to the task design.
13
- */
14
- function TaskDiscussion(handle) {
15
- let requested = handle.props.conversationId;
16
- let selected = requested;
17
- let explicit = Boolean(requested);
18
- let creating = false;
19
- let picking = false;
20
- let draftItemIds = [handle.props.itemId];
21
- let comments = createProjectComments({
22
- store: projectStore(handle.props.project),
23
- token: handle.props.project.token,
24
- signal: handle.signal,
25
- filter: { itemId: handle.props.itemId },
26
- initial: handle.props.initial,
27
- onChange() {
28
- if (!handle.signal.aborted) handle.update();
29
- }
30
- });
31
- if (comments.loaded) selected ??= comments.snapshot.threads[0]?.id;
32
- let mount = ref(() => {
33
- if (comments.loaded) return;
34
- comments.load().then(() => {
35
- selected ??= comments.snapshot.threads[0]?.id;
36
- if (!handle.signal.aborted) handle.update();
37
- });
38
- });
39
- async function write(change) {
40
- let result = await comments.write(change);
41
- await handle.props.onChange?.();
42
- return result;
43
- }
44
- function threadLinks(thread) {
45
- return {
46
- project: handle.props.project,
47
- thread,
48
- items: handle.props.items,
49
- canLink: comments.snapshot.capabilities.link,
50
- busy: comments.busy,
51
- onChange: async () => {
52
- await comments.load();
53
- if (comments.snapshot.threads.some((item) => item.id === thread.id)) await comments.loadThread(thread.id, true);
54
- await handle.props.onChange?.();
55
- }
56
- };
57
- }
58
- function links(thread) {
59
- return /* @__PURE__ */ jsxs("div", {
60
- class: "flex items-start justify-between gap-2",
61
- children: [handle.props.renderThreadLinks ? handle.props.renderThreadLinks(thread) : /* @__PURE__ */ jsx(ProjectThreadLinks, {
62
- part: thread.anchor ? "links" : void 0,
63
- ...threadLinks(thread)
64
- }), !thread.anchor && comments.snapshot.capabilities.reply && comments.snapshot.threads.length > 1 ? /* @__PURE__ */ jsx(Button, {
65
- variant: "ghost",
66
- size: "small",
67
- disabled: comments.busy,
68
- "aria-pressed": !creating && selected === thread.id,
69
- mix: on("click", () => {
70
- selected = thread.id;
71
- explicit = true;
72
- creating = false;
73
- handle.update();
74
- }),
75
- children: t("app.client.taskDiscussion.reply")
76
- }) : null]
77
- });
78
- }
79
- function heading(thread) {
80
- return /* @__PURE__ */ jsx(CommentThreadHeading, {
81
- location: thread.anchor ? conversationLabel(thread) : t("app.client.taskDiscussion.discussion"),
82
- actions: handle.props.renderThreadLinks ? void 0 : /* @__PURE__ */ jsx(ProjectThreadLinks, {
83
- part: "action",
84
- ...threadLinks(thread)
85
- }),
86
- status: thread.status,
87
- busy: comments.busy,
88
- onStatusChange: comments.snapshot.capabilities.status ? (status) => {
89
- write({
90
- id: thread.id,
91
- status
92
- }).catch(() => {});
93
- } : void 0
94
- });
95
- }
96
- return () => {
97
- if (requested !== handle.props.conversationId) {
98
- requested = handle.props.conversationId;
99
- explicit = Boolean(requested);
100
- if (requested) {
101
- selected = requested;
102
- creating = false;
103
- }
104
- }
105
- let { threads, capabilities } = comments.snapshot;
106
- let reply = creating ? void 0 : threads.find((thread) => thread.id === selected && !thread.anchor) ?? (explicit ? void 0 : threads.find((thread) => !thread.anchor && thread.status !== "resolved") ?? (!capabilities.create ? threads.find((thread) => !thread.anchor) : void 0));
107
- let replyPending = !creating && (explicit && (!reply || !capabilities.reply) || Boolean(reply && !reply.messagesLoaded));
108
- if (comments.ready && !comments.busy && !comments.loading) handle.queueTask(() => {
109
- if (explicit && selected) comments.loadThread(selected);
110
- for (let thread of comments.snapshot.threads) if (!thread.messagesLoaded && (!thread.anchor || thread.id === selected)) comments.loadThread(thread.id);
111
- });
112
- let count = threads.reduce((sum, thread) => sum + thread.messageCount, 0);
113
- return /* @__PURE__ */ jsxs("section", {
114
- class: "flex min-w-0 flex-col gap-3",
115
- "aria-label": t("app.client.taskDiscussion.taskDiscussion"),
116
- mix: mount,
117
- children: [
118
- /* @__PURE__ */ jsxs("div", {
119
- class: "flex items-center gap-2",
120
- children: [
121
- /* @__PURE__ */ jsx(Text, {
122
- as: "h3",
123
- variant: "ui",
124
- weight: "medium",
125
- children: t("app.client.taskDiscussion.discussion")
126
- }),
127
- count ? /* @__PURE__ */ jsx(Text, {
128
- variant: "ui-caption",
129
- tone: "muted",
130
- children: count
131
- }) : null,
132
- comments.ready && capabilities.create && threads.length ? /* @__PURE__ */ jsx(Button, {
133
- variant: "ghost",
134
- size: "small",
135
- class: "ms-auto",
136
- disabled: comments.busy || creating,
137
- mix: on("click", () => {
138
- creating = true;
139
- handle.update();
140
- }),
141
- children: t("app.client.taskDiscussion.newDiscussion")
142
- }) : null
143
- ]
144
- }),
145
- comments.error ? /* @__PURE__ */ jsxs("div", {
146
- class: "flex flex-col items-start gap-2",
147
- children: [/* @__PURE__ */ jsx(Text, {
148
- variant: "ui-caption",
149
- role: "alert",
150
- children: comments.error
151
- }), /* @__PURE__ */ jsx(Button, {
152
- variant: "secondary",
153
- size: "small",
154
- mix: on("click", () => {
155
- comments.load().then(() => selected ? comments.loadThread(selected, true) : void 0);
156
- }),
157
- children: t("app.client.taskDiscussion.reloadComments")
158
- })]
159
- }) : null,
160
- !comments.loaded ? /* @__PURE__ */ jsx(Text, {
161
- variant: "ui-caption",
162
- tone: "muted",
163
- role: "status",
164
- children: t("app.client.taskDiscussion.loadingDiscussion")
165
- }) : !comments.ready ? /* @__PURE__ */ jsx(Text, {
166
- variant: "ui-caption",
167
- tone: "muted",
168
- children: t("app.client.taskDiscussion.commentsUnavailable")
169
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [threads.length ? threads.map((thread) => !thread.anchor ? /* @__PURE__ */ jsx(ItemDiscussion, {
170
- thread,
171
- links: links(thread)
172
- }, thread.id) : /* @__PURE__ */ jsx(CommentThreadItem, {
173
- id: thread.id,
174
- expanded: selected === thread.id,
175
- heading: heading(thread),
176
- messages: thread.messages,
177
- messageCount: thread.messageCount,
178
- now: comments.now,
179
- onExpand: () => {
180
- selected = thread.id;
181
- explicit = false;
182
- comments.loadThread(thread.id);
183
- handle.update();
184
- },
185
- renderDetail: () => /* @__PURE__ */ jsx(CommentThreadDetail, {
186
- heading: heading(thread),
187
- messages: thread.messages,
188
- messageCount: thread.messageCount,
189
- messagesLoaded: thread.messagesLoaded,
190
- messagesTruncated: thread.messagesTruncated,
191
- now: comments.now,
192
- busy: comments.busy,
193
- links: links(thread),
194
- onReply: capabilities.reply ? (body, parentId) => write({
195
- id: thread.id,
196
- body,
197
- parentId
198
- }) : void 0,
199
- viewerId: comments.viewer?.id,
200
- onDelete: capabilities.delete ? async (messageId) => {
201
- let whole = thread.messages[0]?.id === messageId;
202
- await comments.remove(thread.id, messageId);
203
- if (whole && selected === thread.id) selected = void 0;
204
- handle.update();
205
- } : void 0
206
- })
207
- }, thread.id)) : /* @__PURE__ */ jsx(Text, {
208
- variant: "ui-caption",
209
- tone: "muted",
210
- children: t("app.client.taskDiscussion.noDiscussionYet")
211
- }), capabilities.create || capabilities.reply && (explicit || threads.some((thread) => !thread.anchor)) ? /* @__PURE__ */ jsxs("div", {
212
- class: "flex min-w-0 flex-col gap-2",
213
- children: [capabilities.create && (creating || !explicit && !threads.length) ? /* @__PURE__ */ jsxs("div", {
214
- class: "flex items-start justify-between gap-2",
215
- "aria-label": t("app.client.taskDiscussion.newDiscussionTasks"),
216
- children: [/* @__PURE__ */ jsx(ThreadTaskLinks, {
217
- itemIds: draftItemIds,
218
- items: handle.props.items ?? [],
219
- href: taskHref,
220
- busy: comments.busy,
221
- onEdit: capabilities.link ? () => {
222
- picking = true;
223
- handle.update();
224
- } : void 0
225
- }), creating ? /* @__PURE__ */ jsx(Button, {
226
- variant: "ghost",
227
- size: "small",
228
- disabled: comments.busy,
229
- mix: on("click", () => {
230
- creating = false;
231
- handle.update();
232
- }),
233
- children: t("app.client.taskDiscussion.cancel")
234
- }) : null]
235
- }) : threads.length > 1 ? /* @__PURE__ */ jsx(Text, {
236
- variant: "ui-caption",
237
- tone: "muted",
238
- children: t("app.client.taskDiscussion.replyingTo", { name: reply?.messages[0]?.author.name ?? t("app.client.taskDiscussion.theDiscussion") })
239
- }) : null, /* @__PURE__ */ jsx(CommentComposer, {
240
- label: t("app.client.taskDiscussion.addTaskComment"),
241
- placeholder: t("app.client.taskDiscussion.addContextPlaceholder"),
242
- busy: comments.busy || replyPending,
243
- class: "w-full",
244
- onSubmit: async (body) => {
245
- if (replyPending) throw new Error(t("app.client.taskDiscussion.loadBeforeReplying"));
246
- if (reply && capabilities.reply) {
247
- await write({
248
- id: reply.id,
249
- body
250
- });
251
- return;
252
- }
253
- selected = (await write({
254
- anchor: null,
255
- itemIds: draftItemIds,
256
- body
257
- })).thread.id;
258
- explicit = Boolean(selected);
259
- creating = false;
260
- draftItemIds = [handle.props.itemId];
261
- handle.update();
262
- }
263
- }, "task-composer")]
264
- }) : null] }),
265
- picking ? /* @__PURE__ */ jsx(ProjectTaskPicker, {
266
- project: handle.props.project,
267
- selected: draftItemIds,
268
- required: [handle.props.itemId],
269
- onSave: async (itemIds) => {
270
- draftItemIds = itemIds;
271
- handle.update();
272
- },
273
- onClose: () => {
274
- picking = false;
275
- handle.update();
276
- }
277
- }) : null
278
- ]
279
- });
280
- };
281
- }
282
- //#endregion
283
- //#region app/client/project-tasks.tsx
284
- function changedSince(row, item) {
285
- if (row.revision !== null) return row.revision !== item.revision;
286
- return Object.entries(row.values).some(([field, value]) => JSON.stringify(value) !== JSON.stringify(item.values[field]));
287
- }
288
- async function loadTask(token, id, schema, signal, conversationId) {
289
- let [value, discussion, resolved] = await Promise.all([
290
- requestTasks(routes.tasksApi.show.href({ id }), token, signal, (value) => value),
291
- loadItemComments(token, id, signal, conversationId),
292
- schema
293
- ]);
294
- let detail = parseWorkDetail(value, resolved);
295
- if (detail.item.id !== id) throw new Error(t("app.client.projectTasks.differentTaskRefresh"));
296
- return {
297
- detail,
298
- discussion
299
- };
300
- }
301
- /**
302
- * Opens the project's stored board, reading it from the companion only when this browser has
303
- * none yet, and reads the task the URL opens with its discussions in parallel. An adapter
304
- * failure is kept as data for the board.
305
- * @param project - The project whose store and companion to read.
306
- * @param signal - The screen's lifetime.
307
- * @param href - The screen's location; a task page also reads that task.
308
- * @param conversationId - A discussion the URL selects on that task page.
309
- * @returns The store, or the error the board shows until a refresh succeeds.
310
- */
311
- async function loadTasks(project, signal, href, conversationId) {
312
- let store = await projectStore(project);
313
- let stored = storedWorkspace(store);
314
- let workspace = stored ? Promise.resolve(stored) : projectWork(project).refresh(signal);
315
- let id = href ? selectedTask(href) : null;
316
- let open = (schema) => loadTask(project.token, id, schema, signal, conversationId);
317
- let taskError = "";
318
- let task = id ? open(workspace.then((value) => value.schema)).catch((cause) => {
319
- if (!stored || cause instanceof TaskRequestError) throw cause;
320
- return open(projectWork(project).refresh(signal).then((value) => value.schema));
321
- }).catch((cause) => {
322
- taskError = cause instanceof Error ? cause.message : t("app.client.projectTasks.couldNotLoadTask");
323
- return null;
324
- }) : null;
325
- try {
326
- await workspace;
327
- let opened = await task;
328
- return {
329
- store,
330
- stored: Boolean(stored),
331
- error: "",
332
- task: opened,
333
- taskError
334
- };
335
- } catch (cause) {
336
- signal.throwIfAborted();
337
- return {
338
- store,
339
- stored: false,
340
- error: cause instanceof Error ? cause.message : t("app.client.projectTasks.couldNotLoadTasks")
341
- };
342
- }
343
- }
344
- /**
345
- * The project's work board, its task details and task writes.
346
- * @param handle - The project and its first board.
347
- * @returns The board render function.
348
- */
349
- function ProjectTasks(handle) {
350
- let project = handle.props.project;
351
- let store = handle.props.initial.store;
352
- let work = projectWork(project);
353
- let workspace = storedWorkspace(store);
354
- let stored = () => {
355
- let version = workspace?.snapshot.schemaVersion;
356
- workspace = storedWorkspace(store);
357
- let row = detail ? store.work.get(detail.item.id) : void 0;
358
- if (row && !row.$hasPendingWrites && changedSince(plainRow(row), detail.item)) loadDetail();
359
- else if (!detail && detailError && workspace?.snapshot.schemaVersion !== version) loadDetail();
360
- changed();
361
- };
362
- let subscriptions = [store.boards.subscribeChanges(stored), store.work.subscribeChanges(stored)];
363
- let selectedId = selectedTask(handle.props.href);
364
- let opened = handle.props.initial.task;
365
- let detail = opened?.detail.item.id === selectedId ? opened.detail : null;
366
- let discussion = detail ? opened.discussion : void 0;
367
- let loading = false;
368
- let detailError = handle.props.initial.task ? "" : handle.props.initial.taskError ?? "";
369
- let detailLoading = selectedId !== null && !detail && !detailError;
370
- let error = handle.props.initial.error;
371
- let listRequest = null;
372
- let detailRequest = null;
373
- let writeRequests = /* @__PURE__ */ new Set();
374
- let renderedHref = handle.props.href;
375
- let pendingHref;
376
- let deferredLocation = false;
377
- let mounted = false;
378
- function pending(open) {
379
- let row = open ? store.work.get(open.item.id) : void 0;
380
- if (!open || !row?.$hasPendingWrites) return open;
381
- let values = {
382
- ...open.item.values,
383
- ...plainRow(row).values
384
- };
385
- return {
386
- ...open,
387
- item: {
388
- ...open.item,
389
- values
390
- }
391
- };
392
- }
393
- function currentSchema() {
394
- if (!workspace) throw new Error(t("app.client.projectTasks.refreshBeforeChanges"));
395
- return workspace.schema;
396
- }
397
- async function write(url, body, parse) {
398
- let controller = new AbortController();
399
- writeRequests.add(controller);
400
- try {
401
- return await requestTasks(url, project.token, AbortSignal.any([handle.signal, controller.signal]), parse, fetch, { body });
402
- } finally {
403
- writeRequests.delete(controller);
404
- }
405
- }
406
- async function accepted(issue) {
407
- await writeRows(store.work, { upsert: [issue] });
408
- if (detail?.item.id === issue.id) detail = {
409
- ...detail,
410
- item: issue
411
- };
412
- changed();
413
- }
414
- async function refreshAfterWrite() {
415
- await load();
416
- if (selectedId) await loadDetail();
417
- }
418
- let queues = /* @__PURE__ */ new Map();
419
- async function save(url, body, predict, id) {
420
- let previous = id ? queues.get(id) : void 0;
421
- let settled = Promise.withResolvers();
422
- if (id) queues.set(id, settled.promise);
423
- try {
424
- let item = await writeOptimistically(predict, async () => {
425
- let prior = await previous;
426
- let from = "expectedRevision" in body ? body.expectedRevision : void 0;
427
- let result = await write(url, prior && from === prior.from && prior.to ? {
428
- ...body,
429
- expectedRevision: prior.to
430
- } : body, (value) => parseWorkMutationResult(value, currentSchema()));
431
- if (id && result.item.id !== id) throw new Error(t("app.client.projectTasks.differentTaskRefreshRetry"));
432
- await accepted(result.item);
433
- settled.resolve(from ? {
434
- from,
435
- to: result.item.revision
436
- } : null);
437
- return result.item;
438
- });
439
- if (selectedId) await loadDetail();
440
- return item;
441
- } catch (cause) {
442
- settled.resolve(null);
443
- if (!handle.signal.aborted) toast.error(t("app.client.projectTasks.undidTaskChange"), { description: cause instanceof Error ? cause.message : t("app.client.projectTasks.writeNotConfirmed") });
444
- refreshAfterWrite();
445
- throw cause;
446
- } finally {
447
- if (id && queues.get(id) === settled.promise) queues.delete(id);
448
- }
449
- }
450
- function predicted(id, next) {
451
- return () => {
452
- let row = store.work.get(id);
453
- if (!row) return;
454
- let { values } = next(plainRow(row));
455
- store.work.update(id, (draft) => {
456
- draft.values = values;
457
- });
458
- };
459
- }
460
- let operations = {
461
- create: (draft) => save(routes.tasksApi.create.href(), draft, () => {
462
- store.work.insert(predictWorkCreate(currentSchema(), `pending-${crypto.randomUUID()}`, draft));
463
- }),
464
- update: (id, action) => save(routes.tasksApi.update.href({ id }), action, predicted(id, (item) => predictWorkUpdate(item, action)), id),
465
- act: (id, action) => save(routes.tasksApi.mutate.href({ id }), action, predicted(id, (item) => predictWorkAction(currentSchema(), item, action)), id),
466
- get: async (id) => {
467
- let next = await requestTasks(routes.tasksApi.show.href({ id }), project.token, handle.signal, (value) => parseWorkDetail(value, currentSchema()));
468
- if (next.item.id !== id) throw new Error(t("app.client.projectTasks.differentTaskRefresh"));
469
- return next.item;
470
- },
471
- section: (id, section, signal = handle.signal) => requestTasks(routes.tasksApi.section.href({
472
- id,
473
- section
474
- }), project.token, AbortSignal.any([handle.signal, signal]), (value) => parseSectionData(value, currentSchema())),
475
- sync: async (action) => {
476
- try {
477
- let result = await write(routes.tasksApi.sync.href(), action, parseWorkSyncResult);
478
- await refreshAfterWrite();
479
- return result;
480
- } catch (cause) {
481
- refreshAfterWrite();
482
- throw cause;
483
- }
484
- }
485
- };
486
- function changed() {
487
- if (!handle.signal.aborted) handle.update();
488
- }
489
- async function load() {
490
- listRequest?.abort();
491
- let controller = new AbortController();
492
- listRequest = controller;
493
- loading = true;
494
- error = "";
495
- changed();
496
- try {
497
- await work.refresh(AbortSignal.any([handle.signal, controller.signal]));
498
- } catch (cause) {
499
- if (!controller.signal.aborted && !handle.signal.aborted) error = cause instanceof Error ? cause.message : t("app.client.projectTasks.couldNotLoadTasks");
500
- } finally {
501
- if (listRequest === controller) {
502
- loading = false;
503
- changed();
504
- }
505
- }
506
- }
507
- async function loadDetail() {
508
- detailRequest?.abort();
509
- if (!selectedId) {
510
- detail = null;
511
- detailLoading = false;
512
- detailError = "";
513
- changed();
514
- return;
515
- }
516
- let id = selectedId;
517
- let controller = new AbortController();
518
- detailRequest = controller;
519
- let reopening = detail?.item.id !== id;
520
- if (reopening) detail = null;
521
- detailLoading = true;
522
- detailError = "";
523
- changed();
524
- try {
525
- let signal = AbortSignal.any([handle.signal, controller.signal]);
526
- if (reopening) {
527
- let next = await loadTask(project.token, id, currentSchema(), signal, handle.props.conversationId);
528
- if (controller.signal.aborted || id !== selectedId) return;
529
- detail = next.detail;
530
- discussion = next.discussion;
531
- return;
532
- }
533
- let next = await requestTasks(routes.tasksApi.show.href({ id }), project.token, signal, (value) => parseWorkDetail(value, currentSchema()));
534
- if (controller.signal.aborted || id !== selectedId) return;
535
- if (next.item.id !== id) throw new Error(t("app.client.projectTasks.differentTaskRefresh"));
536
- detail = next;
537
- } catch (cause) {
538
- if (!controller.signal.aborted && !handle.signal.aborted && id === selectedId) detailError = cause instanceof Error ? cause.message : t("app.client.projectTasks.couldNotLoadTask");
539
- } finally {
540
- if (detailRequest === controller) {
541
- detailLoading = false;
542
- changed();
543
- }
544
- }
545
- }
546
- function select(id, history = true) {
547
- if (id === selectedId) return;
548
- selectedId = id;
549
- if (history) location(false);
550
- loadDetail();
551
- changed();
552
- }
553
- function location(replace) {
554
- let root = taskHref(null);
555
- if (!handle.props.active || !readConversationLocation(window.location.href) && window.location.pathname !== root && !window.location.pathname.startsWith(`${root}/`)) {
556
- deferredLocation = true;
557
- return;
558
- }
559
- deferredLocation = false;
560
- let href = taskHref(selectedId);
561
- if (window.location.pathname + window.location.search + window.location.hash === href) return;
562
- pendingHref = new URL(href, window.location.origin).href;
563
- window.history[replace ? "replaceState" : "pushState"]({
564
- ...window.history.state,
565
- budTask: selectedId
566
- }, "", href);
567
- }
568
- let mount = ref((_node, signal) => {
569
- mounted = true;
570
- let loaded = selectedId;
571
- selectedId = selectedTask(handle.props.href);
572
- if (detail?.item.id !== selectedId && !(detailError && loaded === selectedId)) loadDetail();
573
- if (handle.props.initial.stored) load();
574
- signal.addEventListener("abort", () => {
575
- for (let subscription of subscriptions) subscription.unsubscribe();
576
- listRequest?.abort();
577
- detailRequest?.abort();
578
- for (let controller of writeRequests) controller.abort();
579
- }, { once: true });
580
- });
581
- return () => {
582
- if (mounted && renderedHref !== handle.props.href) {
583
- renderedHref = handle.props.href;
584
- if (pendingHref !== renderedHref) {
585
- deferredLocation = false;
586
- let id = selectedTask(renderedHref);
587
- if (id !== selectedId) handle.queueTask(() => select(id, false));
588
- }
589
- pendingHref = void 0;
590
- }
591
- if (deferredLocation && handle.props.active) handle.queueTask(() => location(true));
592
- let current = workspace;
593
- let linked = current?.snapshot.items.map((item) => ({
594
- id: item.id,
595
- title: taskRole(item, current.schema, "title") || void 0
596
- }));
597
- return /* @__PURE__ */ jsx("div", {
598
- class: "h-full min-h-0",
599
- mix: mount,
600
- children: /* @__PURE__ */ jsx(TaskWorkspace, {
601
- projectName: project.name,
602
- project: {
603
- id: project.id,
604
- name: project.name
605
- },
606
- design: handle.props.design,
607
- storageKey: `bud:tasks:${project.id}:v1`,
608
- snapshot: current?.snapshot ?? null,
609
- schema: current?.schema ?? null,
610
- loading,
611
- error,
612
- selectedId,
613
- detail: pending(detail),
614
- discussion: detail ? /* @__PURE__ */ jsx(TaskDiscussion, {
615
- project,
616
- itemId: detail.item.id,
617
- initial: discussion,
618
- items: linked,
619
- conversationId: handle.props.conversationId,
620
- onChange: refreshAfterWrite
621
- }, detail.item.id) : void 0,
622
- detailLoading,
623
- detailError,
624
- operations,
625
- onSelect: select,
626
- onReload: () => {
627
- load();
628
- },
629
- onReloadDetail: () => {
630
- loadDetail();
631
- }
632
- })
633
- });
634
- };
635
- }
636
- //#endregion
637
- //#region app/screens/tasks.tsx
638
- var tasks_default = defineScreen({
639
- routes: taskRoutes,
640
- load: ({ project, signal, url, conversation }) => loadTasks(project, signal, url.href, conversation?.id),
641
- render: (tasks, { project, url, active, taskDesign, conversation }) => /* @__PURE__ */ jsx(ProjectTasks, {
642
- project,
643
- initial: tasks,
644
- design: taskDesign,
645
- href: url.href,
646
- active,
647
- conversationId: conversation?.id
648
- })
649
- });
650
- //#endregion
651
- export { tasks_default as default };