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