@bud.sh/app 0.4.0 → 0.5.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 (74) hide show
  1. package/dist/client/assets/bud/design.js +1 -1
  2. package/dist/client/assets/{canvas-C8hyPWoT.js → canvas-Cu6R6xxT.js} +1 -1
  3. package/dist/client/assets/difference-worker-jfTSn-Tw.js +8 -0
  4. package/dist/client/assets/{edit-B050x0rV.js → edit-DUotSxjS.js} +12 -12
  5. package/dist/client/assets/files-js6ElfkU.js +1 -0
  6. package/dist/client/assets/i18n-DxqFr2mi.js +1 -0
  7. package/dist/client/assets/index-CFFxP0gS.css +2 -0
  8. package/dist/client/assets/{index-Dpxjiok1.js → index-ChANGtl-.js} +2 -2
  9. package/dist/client/assets/lru-DzKYIXMA.js +1 -0
  10. package/dist/client/assets/pages-6_lhiQmz.js +1 -0
  11. package/dist/client/assets/{project-branches-DzXdaT9L.js → project-branches-DO897zM6.js} +1 -1
  12. package/dist/client/assets/project-comments-DFFKVpia.js +1 -0
  13. package/dist/client/assets/project-thread-links-NLFWQRD2.js +1 -0
  14. package/dist/client/assets/{project-threads-CHGG-6fo.js → project-threads-Lay351p4.js} +105 -105
  15. package/dist/client/assets/{project-work-L5fYMSOu.js → project-work-du0Y-L5Y.js} +1 -1
  16. package/dist/client/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  17. package/dist/client/assets/task-design-Avb2RO4w.js +23 -0
  18. package/dist/client/assets/tasks-CA-E8bbK.js +1 -0
  19. package/dist/client/assets/{tasks-request-DGigNdyK.js → tasks-request-C55IeuiP.js} +1 -1
  20. package/dist/client/assets/workspace-Cw5HvX5d.js +2 -0
  21. package/dist/client/index.html +3 -3
  22. package/dist/hosted/assets/{canvas-MWtOwaWm.js → canvas-V36732ER.js} +6 -4
  23. package/dist/hosted/assets/{comment-data-DsgJWq4n.js → comment-data-CF2ZV_3k.js} +4162 -3659
  24. package/dist/hosted/assets/{files-Wvo11G2A.js → files-DjSCjfU7.js} +5 -3
  25. package/dist/hosted/assets/{pages-BAmRFVtU.js → pages-BzjzeuPu.js} +3 -3
  26. package/dist/hosted/assets/{project-branches-BNloVosX.js → project-branches-BT97J9i7.js} +1 -1
  27. package/dist/hosted/assets/{project-thread-links-BmY8HQXt.js → project-thread-links-BRLgByEe.js} +2 -2
  28. package/dist/hosted/assets/{screen-QmqmI2Aq.js → screen-CKp2C7kx.js} +68 -137
  29. package/dist/hosted/assets/{tasks-C8iWPrWg.js → tasks-Dj7AyBJB.js} +12 -5
  30. package/dist/hosted/assets/{tasks-request-C89fN1dK.js → tasks-request-DZob623r.js} +1 -1
  31. package/dist/hosted/hosted.js +174 -18
  32. package/dist/node/assets/{src-BdvZ2mYc.js → src-CerrKjID.js} +57 -1
  33. package/dist/node/design.js +969 -809
  34. package/dist/node/server.js +25 -3
  35. package/dist/node-host/assets/{canvas-D_F8SIlm.js → canvas-TRUfxfL5.js} +6 -4
  36. package/dist/node-host/assets/{comment-data-q_8INiZz.js → comment-data-DyIZNpfG.js} +4143 -3640
  37. package/dist/node-host/assets/{files-xazOrNCX.js → files-Dh2TiQZq.js} +5 -3
  38. package/dist/node-host/assets/{pages-D6VkmdfQ.js → pages-BxpjbYhs.js} +3 -3
  39. package/dist/node-host/assets/{project-branches-C26hwmo3.js → project-branches-B7HUGiVK.js} +1 -1
  40. package/dist/node-host/assets/{project-thread-links-BOzjws5W.js → project-thread-links-CauJF0Cz.js} +2 -2
  41. package/dist/node-host/assets/{screen-Dog-YByu.js → screen-CZXWJEds.js} +68 -137
  42. package/dist/node-host/assets/{tasks-A1_bpb3_.js → tasks-Bs1IOpeU.js} +12 -5
  43. package/dist/node-host/assets/{tasks-request-Bie1PpQP.js → tasks-request-ZWmbc8fE.js} +1 -1
  44. package/dist/node-host/node-host.js +174 -18
  45. package/dist/server/assets/{canvas-Dn4RYoU2.js → canvas-Dc1mYCwQ.js} +7 -5
  46. package/dist/server/assets/{comment-data-B0OLHBGp.js → comment-data-CsjukwAW.js} +3911 -3619
  47. package/dist/server/assets/{events-6z5v7Uca.js → events-iHpLGr3k.js} +213 -2
  48. package/dist/server/assets/{files-C5Vilfe4.js → files-E9mSFeoc.js} +6 -4
  49. package/dist/server/assets/{listener-o1STuLju.js → listener-DN5TyOjJ.js} +45 -11
  50. package/dist/server/assets/{pages-BNa0MHEc.js → pages-BBBXyJWV.js} +4 -4
  51. package/dist/server/assets/{project-branches-GtQYyOH5.js → project-branches-B0YIK6Ur.js} +2 -2
  52. package/dist/server/assets/{project-thread-links-Nfz9NIny.js → project-thread-links-CBOpf6zk.js} +3 -3
  53. package/dist/server/assets/{project-work-Cy_BpHwR.js → project-work-qSKFO5yj.js} +3 -3
  54. package/dist/server/assets/{screen-BtcJmZwj.js → screen-BIQA2mr4.js} +69 -138
  55. package/dist/server/assets/{server-CYvuOSBA.js → server-DxK-UB_Z.js} +30 -4
  56. package/dist/server/assets/{services-DK4fqrlF.js → services-BRrgXYlT.js} +105 -9
  57. package/dist/server/assets/{tasks-B82tHwyQ.js → tasks-CiXodsLN.js} +14 -7
  58. package/dist/server/assets/{tasks-request-BtJBlkxV.js → tasks-request-DhwCzpRi.js} +2 -2
  59. package/dist/server/migration.js +3 -3
  60. package/dist/server/runtime.js +3 -3
  61. package/dist/server/server.js +2 -2
  62. package/dist/types/server.d.ts +11 -1
  63. package/package.json +1 -1
  64. package/dist/client/assets/difference-worker-BfF4_N3e.js +0 -8
  65. package/dist/client/assets/files-D_Tm7gCK.js +0 -1
  66. package/dist/client/assets/i18n-BtfXum8P.js +0 -1
  67. package/dist/client/assets/index-CjhJtepP.css +0 -2
  68. package/dist/client/assets/lru-BkhKJhvt.js +0 -1
  69. package/dist/client/assets/pages-B-_CKvDS.js +0 -1
  70. package/dist/client/assets/project-comments-CQkB2jbE.js +0 -1
  71. package/dist/client/assets/project-thread-links-BN7zL0AX.js +0 -1
  72. package/dist/client/assets/task-design-DYqcqq1b.js +0 -23
  73. package/dist/client/assets/tasks-A2FKo3_h.js +0 -1
  74. package/dist/client/assets/workspace-CvWmU2RK.js +0 -2
@@ -1,144 +1,21 @@
1
- import { D as parseThreadId, E as parseThreadFilter, i as routes, j as parseThreadSnapshot, w as parseThread } from "./events-6z5v7Uca.js";
2
- import { Tn as t, _ as requestComments, a as saveThreads, b as writeOptimistically, bn as toast, g as CommentsRequestError, i as saveThread, l as projectStore, n as unavailableComments, o as threadMatches, y as plainRow } from "./comment-data-B0OLHBGp.js";
3
- //#region ../work/src/lib/predict.ts
4
- /**
5
- * How a new task shows until its adapter confirms it: the requested values, and the creation
6
- * time for timestamps the adapter sets, under a provisional identity with no revision,
7
- * relations or counts yet.
8
- * @param schema - The board's schema.
9
- * @param id - A provisional identity no adapter uses.
10
- * @param create - The create request.
11
- * @param now - The creation time.
12
- * @returns The provisional task.
13
- */
14
- function predictWorkCreate(schema, id, create, now = /* @__PURE__ */ new Date()) {
15
- let values = { ...create.values };
16
- for (let field of schema.fields) if ((field.role === "created" || field.role === "updated") && values[field.id] === void 0) values[field.id] = field.kind === "date" ? now.toISOString().slice(0, 10) : now.toISOString();
17
- return {
18
- id,
19
- revision: null,
20
- values,
21
- relations: [],
22
- counts: {},
23
- contentLoaded: false,
24
- extra: null
25
- };
26
- }
27
- /**
28
- * How a task shows until its adapter confirms an edit: the edited values over its own.
29
- * @param item - The task as last confirmed.
30
- * @param update - The update request.
31
- * @returns The predicted task.
32
- */
33
- function predictWorkUpdate(item, update) {
34
- return {
35
- ...item,
36
- values: {
37
- ...item.values,
38
- ...update.values
39
- }
40
- };
41
- }
42
- var targets = {
43
- close: ["done", "cancelled"],
44
- reopen: [
45
- "unstarted",
46
- "backlog",
47
- "started"
48
- ]
49
- };
50
- /**
51
- * How a task shows until its adapter confirms an action. Closing and reopening move it to
52
- * the schema's first matching status; otherwise only input naming the task's own fields
53
- * applies, since an action's effects belong to the adapter.
54
- * @param schema - The board's schema.
55
- * @param item - The task as last confirmed.
56
- * @param action - The action request.
57
- * @returns The predicted task.
58
- */
59
- function predictWorkAction(schema, item, action) {
60
- let fields = new Set(schema.fields.map((field) => field.id));
61
- let values = { ...item.values };
62
- for (let [id, value] of Object.entries(action.input)) if (fields.has(id)) values[id] = value;
63
- let kind = schema.actions.find((candidate) => candidate.id === action.action)?.kind;
64
- let categories = kind ? targets[kind] : void 0;
65
- let field = schema.fields.find((candidate) => candidate.role === "status");
66
- let status = categories?.map((category) => schema.statuses.find((candidate) => candidate.category === category)).find(Boolean);
67
- if (field && status) values[field.id] = status.value;
68
- return {
69
- ...item,
70
- values
71
- };
72
- }
73
- /** Who a message shows as until the server, which derives authors from Git, confirms it. */
74
- var pendingAuthor = {
75
- id: "pending",
76
- name: "You",
77
- email: ""
78
- };
79
- function message(id, body, parentId, now) {
80
- return {
81
- id,
82
- body: body.trim(),
83
- author: pendingAuthor,
84
- createdAt: now.toISOString(),
85
- parentId
86
- };
87
- }
88
- /**
89
- * How a new discussion shows until its adapter confirms it.
90
- * @param id - A provisional identity, also used for its first message.
91
- * @param input - The create request.
92
- * @param now - The creation time.
93
- * @returns The provisional discussion.
94
- */
95
- function predictThreadCreate(id, input, now = /* @__PURE__ */ new Date()) {
96
- let createdAt = now.toISOString();
97
- return {
98
- id,
99
- status: "open",
100
- anchor: input.anchor,
101
- itemIds: [...input.itemIds],
102
- messages: [message(id, input.body, null, now)],
103
- messageCount: 1,
104
- messagesLoaded: true,
105
- messagesTruncated: false,
106
- createdAt,
107
- updatedAt: createdAt,
108
- revision: null
1
+ import { F as parseThreadId, P as parseThreadFilter, b as predictThreadReply, i as routes, j as parseThread, v as predictThreadCreate, x as predictThreadStatus, y as predictThreadDelete, z as parseThreadSnapshot } from "./events-iHpLGr3k.js";
2
+ import { En as t, _ as requestComments, a as saveThreads, b as writeOptimistically, bn as toast, g as CommentsRequestError, i as saveThread, l as projectStore, n as unavailableComments, o as threadMatches, x as writeRows, y as plainRow } from "./comment-data-CsjukwAW.js";
3
+ //#region app/client/project-comments.ts
4
+ var viewers = /* @__PURE__ */ new Map();
5
+ function parseViewer(value) {
6
+ let viewer = value && typeof value === "object" && "viewer" in value ? value.viewer : void 0;
7
+ if (viewer === null) return null;
8
+ if (viewer && typeof viewer === "object" && "id" in viewer && "name" in viewer && typeof viewer.id === "string" && typeof viewer.name === "string" && viewer.id.length <= 256 && viewer.name.length <= 1e3) return {
9
+ id: viewer.id,
10
+ name: viewer.name
109
11
  };
12
+ throw new TypeError("Invalid viewer.");
110
13
  }
111
- /**
112
- * How a discussion shows until its adapter confirms a reply.
113
- * @param thread - The discussion as last confirmed.
114
- * @param id - A provisional message identity.
115
- * @param input - The reply request.
116
- * @param now - The reply time.
117
- * @returns The predicted discussion.
118
- */
119
- function predictThreadReply(thread, id, input, now = /* @__PURE__ */ new Date()) {
120
- return {
121
- ...thread,
122
- messages: thread.messagesLoaded ? [...thread.messages, message(id, input.body, input.parentId, now)] : thread.messages,
123
- messageCount: thread.messageCount + 1,
124
- updatedAt: now.toISOString()
125
- };
14
+ function parseDeletion(value) {
15
+ if (!value || typeof value !== "object" || !("thread" in value)) throw new TypeError("Invalid deletion.");
16
+ return { thread: value.thread === null ? null : parseThread(value.thread) };
126
17
  }
127
18
  /**
128
- * How a discussion shows until its adapter confirms a status change.
129
- * @param thread - The discussion as last confirmed.
130
- * @param input - The status request.
131
- * @returns The predicted discussion.
132
- */
133
- function predictThreadStatus(thread, input) {
134
- return {
135
- ...thread,
136
- status: input.status
137
- };
138
- }
139
- //#endregion
140
- //#region app/client/project-comments.ts
141
- /**
142
19
  * Reads one task's discussions with their messages in a single round, so the task page
143
20
  * renders complete instead of loading its discussion after it appears.
144
21
  * @param token - The current companion capability.
@@ -246,6 +123,21 @@ function createProjectComments(options) {
246
123
  let generation = 0, reading;
247
124
  let details = /* @__PURE__ */ new Map();
248
125
  let detailFailures = /* @__PURE__ */ new Set();
126
+ let viewer = null, askedViewer = false;
127
+ function askViewer() {
128
+ if (askedViewer || !snapshot.capabilities.delete) return;
129
+ askedViewer = true;
130
+ let pending = viewers.get(options.token);
131
+ if (!pending) {
132
+ pending = requestComments(routes.comments.viewer.href(), options.token, new AbortController().signal, parseViewer, options.fetcher).catch(() => null);
133
+ viewers.set(options.token, pending);
134
+ }
135
+ pending.then((value) => {
136
+ viewer = value;
137
+ changed();
138
+ });
139
+ }
140
+ askViewer();
249
141
  let opened = Promise.resolve(options.store).then(async (ready) => {
250
142
  store = ready;
251
143
  if (options.initial?.snapshot && !options.initial.stored) await saveThreads(ready, options.initial.snapshot, filter);
@@ -265,6 +157,7 @@ function createProjectComments(options) {
265
157
  loaded = true;
266
158
  now = Date.now();
267
159
  version++;
160
+ askViewer();
268
161
  changed();
269
162
  }
270
163
  function request(url, signal, parse, body) {
@@ -282,6 +175,12 @@ function createProjectComments(options) {
282
175
  await saveThread(store, thread);
283
176
  view();
284
177
  }
178
+ async function forget(id) {
179
+ await opened;
180
+ members?.delete(id);
181
+ await writeRows(store.threads, { delete: [id] });
182
+ view();
183
+ }
285
184
  function includes(thread) {
286
185
  return threadMatches(thread, filter);
287
186
  }
@@ -414,6 +313,13 @@ function createProjectComments(options) {
414
313
  get ready() {
415
314
  return loaded && snapshot.connection.state === "ready";
416
315
  },
316
+ /**
317
+ * Who this browser comments as, once the server said.
318
+ * @returns The viewer, or null until then or without one.
319
+ */
320
+ get viewer() {
321
+ return viewer;
322
+ },
417
323
  loadingThread: (id) => details.has(id),
418
324
  load,
419
325
  loadThread,
@@ -475,6 +381,31 @@ function createProjectComments(options) {
475
381
  thread: saved
476
382
  };
477
383
  },
384
+ /**
385
+ * Deletes one of the viewer's messages; its first message deletes the whole discussion.
386
+ * @param threadId - The discussion.
387
+ * @param messageId - The message to delete.
388
+ * @returns The snapshot after the confirmed deletion.
389
+ */
390
+ async remove(threadId, messageId) {
391
+ requireCapability("delete");
392
+ let id = parseThreadId(threadId), thread = snapshot.threads.find((item) => item.id === id);
393
+ if (!thread?.revision) throw new Error(t("app.client.projectComments.reloadDiscussionBeforeSaving"));
394
+ let next = predictThreadDelete(thread, messageId);
395
+ return operate(routes.comments.deleteMessage.href({ id }), {
396
+ messageId,
397
+ expectedRevision: thread.revision
398
+ }, parseDeletion, (result) => {
399
+ if (!result.thread) return forget(id);
400
+ if (result.thread.id !== id) throw new CommentsRequestError(t("app.client.projectComments.refreshBeforeTryingAgain"), 409, "uncertain");
401
+ return merge(result.thread);
402
+ }, next ? t("app.client.projectComments.commentDeleted") : t("app.client.projectComments.discussionDeleted"), id, (store) => {
403
+ if (next && store.threads.has(id)) store.threads.update(id, (draft) => {
404
+ draft.messages = next.messages;
405
+ draft.messageCount = next.messageCount;
406
+ });
407
+ });
408
+ },
478
409
  async sync() {
479
410
  requireCapability("sync");
480
411
  return operate(routes.comments.sync.href(), { direction: "both" }, parseThreadSnapshot, adopt, t("app.client.projectComments.commentsSynced"));
@@ -492,4 +423,4 @@ function defineScreen(screen) {
492
423
  return screen;
493
424
  }
494
425
  //#endregion
495
- export { predictWorkAction as a, loadItemComments as i, createProjectComments as n, predictWorkCreate as o, loadComments as r, predictWorkUpdate as s, defineScreen as t };
426
+ export { loadItemComments as i, createProjectComments as n, loadComments as r, defineScreen as t };
@@ -1,5 +1,5 @@
1
1
  import { $ as publishAll, $n as forkUnsafe, $t as provideContext, An as uninterruptible, At as catch_, Bn as effectDiscard, Bt as forever, Cn as tap, Ct as as$1, D as as, Dn as tryPromise, E as FileSystem, Fn as currentTimeMillis, Ft as fail$1, G as make$3, Gn as mergeAll, Gt as map, H as runForEach, Hn as isLayer, Ht as forkScoped, I as fromPubSub, In as build, It as filterOrFail, J as offerUnsafe, Jn as succeed$1, Jt as option, Kn as provide$1, Kt as mapError, L as ignore$1, Ln as buildWithMemoMap, Lt as flatMap, Mn as void_$1, N as empty$1, Nn as withFiber, Nt as die, O as callback, On as try_, Ot as catchIf, P as filter, Pn as yieldNow, Pt as ensuring, Q as publish, Qn as close, R as make$2, Rn as effect, Rt as flatten, S as writeTextAtomic, Sn as sync, St as andThen, T as Path, Tn as timeoutOption, Tt as asVoid, U as tap$1, Un as makeMemoMapUnsafe, Ut as gen, V as merge, Vn as empty, Vt as forkIn, W as unwrap$1, Wt as ignore, X as sliding$1, Xn as sync$1, Xt as orElseSucceed, Y as shutdown$1, Yt as orDie, Z as take, Zn as unwrap, Zt as promise, _n as serviceOption, _t as bytes$1, an as runFork, ar as void_, at as runIn, bn as succeedNone, bt as addFinalizer, cn as runPromiseExit, cr as add, dn as runSync, dr as getOrNull, en as provideService, er as makeUnsafe, et as shutdown, fn as runSyncExit, fr as getOrUndefined, gn as scoped, gr as TaggedError, hn as scope, hr as some, in as runCallbackWith, ir as succeed$2, it as interrupt, j as debounce, jn as uninterruptibleMask, jt as clockWith, kn as undefined_$1, kt as catchTag, ln as runPromiseExitWith, lr as getOption, m as projectModuleFile, mn as runSyncWith, mr as none, nr as doneUnsafe, nt as unbounded, on as runForkWith, or as scopeRemoveFinalizerUnsafe, pn as runSyncExitWith, pr as isNone, q as offer, rn as runCallback, rr as make$4, rt as await_, sn as runPromise, sr as Service, tn as result, tr as _await, tt as sliding, u as Git, un as runPromiseWith, ur as make$1, vn as sleep, vt as acquireRelease, w as RuntimePolicy, wn as tapError, wt as asSome, xn as suspend, xt as all, yn as succeed, zn as effectContext, zt as forEach } from "./branch-previews-ClkoJ0lM.js";
2
- import { $ as parseReviewActionRecovery, A as parseThreadReplyInput, An as iconNames, At as filterFileEntries, Bt as isWithinDirectory, Cn as WorkValidationError, Ct as compileSearchPattern, Dn as record$1, Dt as parseFileSearchQuery, E as parseThreadFilter, En as parseWork, Et as searchPathMatches, Gt as list, Ht as parseFilePath, Jt as strict$1, Kt as parseContract, L as applyReviewSelection, N as parseThreadStatusInput, O as parseThreadInput, On as strict, Rt as parseFileIndexQuery, Sn as statusCategory, St as parseReviewSourceQuery, T as parseThreadCapabilities, Tn as nonempty, Ut as pathHierarchy, Vt as parentPath, Wt as integer, X as parseReviewActionIntent, Y as parseReviewActionApply, Zt as parseWorkWorkspace, _n as parseMessage, _t as parseReviewHistoryQuery, an as parseWorkDetail, at as parseMutationIntent, b as MAX_THREADS, bn as fieldByRole, cn as parseWorkMutationResult, dn as parseWorkSnapshot, dt as validateMutationSelection, en as parseSectionData, gn as valueByRole, hn as parseWorkValue, ht as parseReviewDiffQuery, i as routes, in as parseWorkCreate, it as parseMutationApply, j as parseThreadSnapshot, jt as orderFileEntries, k as parseThreadLinkInput, kt as classifyText, lt as parseRecovery, mn as parseWorkUpdate, on as parseWorkItem, pt as parseReviewComparison, qt as revision, r as parseLiveId, rn as parseWorkConnection, sn as parseWorkItemRef, t as RESYNC, tn as parseWorkActionInput, tt as parseFileSaveRequest, un as parseWorkQuery, ut as pathCollisionKey, vn as parseMessageAuthor, w as parseThread, wn as identifier, wt as matchFileText, xn as parseWorkSchema, zt as isProtectedPath } from "./events-6z5v7Uca.js";
2
+ import { $t as list, At as parseReviewSourceQuery, Cn as parseWorkValue, Ct as parseReviewDiffQuery, D as MAX_THREADS, Dn as parseMessage, En as messageDeletionRefusals, Fn as identifier, Ft as parseFileSearchQuery, G as applyReviewSelection, I as parseThreadInput, In as nonempty, Jt as isWithinDirectory, Kt as parseFileIndexQuery, L as parseThreadLinkInput, Ln as parseWork, Lt as classifyText, M as parseThreadCapabilities, Mn as parseWorkSchema, Mt as matchFileText, N as parseThreadDeleteInput, Nn as statusCategory, On as parseMessageAuthor, P as parseThreadFilter, Pn as WorkValidationError, Pt as searchPathMatches, Qt as integer, R as parseThreadReplyInput, Rn as record$1, Rt as filterFileEntries, Sn as parseWorkUpdate, Tn as messageDeletionRefusal, Tt as parseReviewHistoryQuery, V as parseThreadStatusInput, Vn as iconNames, Xt as parseFilePath, Yt as parentPath, Zt as pathHierarchy, _t as parseRecovery, an as parseWorkWorkspace, cn as parseSectionData, dn as parseWorkConnection, en as parseContract, fn as parseWorkCreate, ft as parseMutationApply, gn as parseWorkMutationResult, hn as parseWorkItemRef, i as routes, it as parseReviewActionIntent, j as parseThread, jn as fieldByRole, jt as compileSearchPattern, kn as withoutMessages, ln as parseWorkActionInput, lt as parseFileSaveRequest, mn as parseWorkItem, nn as strict$1, pn as parseWorkDetail, pt as parseMutationIntent, qt as isProtectedPath, r as parseLiveId, rt as parseReviewActionApply, st as parseReviewActionRecovery, t as RESYNC, tn as revision, vn as parseWorkQuery, vt as pathCollisionKey, wn as valueByRole, xt as parseReviewComparison, yn as parseWorkSnapshot, yt as validateMutationSelection, z as parseThreadSnapshot, zn as strict, zt as orderFileEntries } from "./events-iHpLGr3k.js";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import { createHash, randomBytes, randomUUID } from "node:crypto";
5
5
  import { closeSync, constants, fchmodSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readSync, readdirSync, realpathSync, renameSync, rmdirSync, unlinkSync, writeSync } from "node:fs";
@@ -493,10 +493,10 @@ var MemoryStore = class {
493
493
  reply: this.schema.capabilities.threads,
494
494
  status: this.schema.capabilities.threads,
495
495
  link: this.schema.capabilities.threads,
496
+ delete: this.schema.capabilities.threads,
496
497
  ...options.threadCapabilities,
497
498
  sync: false,
498
- edit: false,
499
- delete: false
499
+ edit: false
500
500
  });
501
501
  if (this.schema.capabilities.sync) refused("Memory storage has no remote synchronization.");
502
502
  for (let action of this.schema.actions) if (action.kind === "custom" && !this.options.actions?.[action.id]) refused("Declare an implementation for each custom memory action.");
@@ -809,6 +809,28 @@ function memoryThreads(store) {
809
809
  status: request.status
810
810
  }, at);
811
811
  }),
812
+ deleteMessage: (root, id, value, author) => store.run(root, true, (data, at) => {
813
+ if (!store.capabilities.delete) refused("This adapter does not allow deleting messages.");
814
+ let request = input$3(() => parseThreadDeleteInput(value));
815
+ let trusted = input$3(() => parseMessageAuthor(author));
816
+ let thread = store.thread(data, id);
817
+ expected(thread, request.expectedRevision);
818
+ let refusal = messageDeletionRefusal(thread.messages, request.messageId, trusted.id);
819
+ if (refusal === "missing") throw new WorkError({
820
+ reason: "not-found",
821
+ message: messageDeletionRefusals.missing
822
+ });
823
+ if (refusal) refused(messageDeletionRefusals[refusal]);
824
+ if (thread.messages[0].id === request.messageId) {
825
+ data.threads.delete(id);
826
+ store.touchThreads(data, thread.itemIds, at);
827
+ return null;
828
+ }
829
+ return save(data, {
830
+ ...thread,
831
+ messages: withoutMessages(thread.messages, [request.messageId])
832
+ }, at);
833
+ }),
812
834
  link: (root, id, ids, revision) => link(root, id, ids, revision, false),
813
835
  unlink: (root, id, ids, revision) => link(root, id, ids, revision, true)
814
836
  };
@@ -1267,7 +1289,11 @@ function adapterRegistryLayer(project, builtIns) {
1267
1289
  "setStatus",
1268
1290
  "link",
1269
1291
  "unlink"
1270
- ], ["sync", "import"]);
1292
+ ], [
1293
+ "sync",
1294
+ "import",
1295
+ "deleteMessage"
1296
+ ]);
1271
1297
  let connection = yield* threads.list(root, {}).pipe(flatMap((value) => try_({
1272
1298
  try: () => parseThreadSnapshot(value).connection,
1273
1299
  catch: failure$3(`Adapter ${selected.threads.id} returned an invalid thread connection.`)
@@ -1,6 +1,6 @@
1
1
  import { A as concat, At as catch_, B as mapError$1, Bn as effectDiscard, C as ProcessRunner, Cn as tap, Ct as as, D as as$1, E as FileSystem, En as timeoutOrElse, Et as cached, F as fromEffect, Fn as currentTimeMillis, Ft as fail, Gn as mergeAll, Gt as map, H as runForEach, Ht as forkScoped, It as filterOrFail, Jn as succeed$1, K as makeUnsafe, Kn as provide, Kt as mapError, Lt as flatMap, M as drop, Mn as void_, Mt as context, N as empty, Nt as die, On as try_, Ot as catchIf, Pt as ensuring, R as make, Rn as effect, Sn as sync, St as andThen, T as Path, Tt as asVoid, Ut as gen, W as unwrap, Wn as merge, Wt as ignore, Xt as orElseSucceed, Yn as succeedContext, Zn as unwrap$1, _n as serviceOption, b as nodeLayer, c as previewsLayer, cr as add, ct as String$1, d as gitLayer, dt as Union, f as Project, fr as getOrUndefined, ft as decodeEffect, gn as scoped, gr as TaggedError, gt as optional, ht as isPattern, i as projectFlows, j as debounce, k as changes, kt as catchTag, lt as Struct, mt as isNonEmpty, nn as retry, ot as ArraySchema, pt as isMaxLength, qn as provideMerge, r as branchPreviewsLayer, s as Previews, sr as Service, st as Record, t as BranchPreviews, tn as result, u as Git, ur as make$1, ut as Undefined, w as RuntimePolicy, xn as suspend, xt as all, yn as succeed, yt as acquireUseRelease, z as mapEffect, zn as effectContext, zt as forEach } from "./branch-previews-ClkoJ0lM.js";
2
- import { A as adapterRegistryLayer, D as publishChanges, E as liveChannelLayer, F as WorkAdapter, I as WorkError, M as memoryAdapterLayer, N as parseThreadImport, O as AdapterRegistry, P as ThreadAdapter, R as bundledScreensLayer, T as LiveChannel, _ as storeActivityLayer, c as FileReview, d as fileSearchLayer, f as Files, g as StoreActivity, i as fileSavesLayer, j as defineAdapter, k as adapterModuleLoaderLayer, l as fileReviewLayer, n as fileReviewActionsLayer, o as fileMutationsLayer, p as filesLayer, s as fileWriteLockLayer, v as storeLiveLayer, z as sourceScreensLayer } from "./server-CYvuOSBA.js";
3
- import { A as parseThreadReplyInput, C as parseAnchor, Cn as WorkValidationError, E as parseThreadFilter, M as parseThreadStatus, N as parseThreadStatusInput, O as parseThreadInput, _n as parseMessage, a as CommentMergeError, an as parseWorkDetail, b as MAX_THREADS, c as parseCommentInput, cn as parseWorkMutationResult, d as parseCommentThread, dn as parseWorkSnapshot, en as parseSectionData, f as revision, fn as parseWorkSync, in as parseWorkCreate, j as parseThreadSnapshot, k as parseThreadLinkInput, l as conversationId, mn as parseWorkUpdate, nn as parseWorkActivity, o as mergeThreads, on as parseWorkItem, p as text$2, pn as parseWorkSyncResult, rn as parseWorkConnection, s as stableJson, tn as parseWorkActionInput, u as object$1, un as parseWorkQuery, vn as parseMessageAuthor, w as parseThread, xn as parseWorkSchema } from "./events-6z5v7Uca.js";
2
+ import { A as adapterRegistryLayer, D as publishChanges, E as liveChannelLayer, F as WorkAdapter, I as WorkError, M as memoryAdapterLayer, N as parseThreadImport, O as AdapterRegistry, P as ThreadAdapter, R as bundledScreensLayer, T as LiveChannel, _ as storeActivityLayer, c as FileReview, d as fileSearchLayer, f as Files, g as StoreActivity, i as fileSavesLayer, j as defineAdapter, k as adapterModuleLoaderLayer, l as fileReviewLayer, n as fileReviewActionsLayer, o as fileMutationsLayer, p as filesLayer, s as fileWriteLockLayer, v as storeLiveLayer, z as sourceScreensLayer } from "./server-DxK-UB_Z.js";
3
+ import { A as parseAnchor, B as parseThreadStatus, D as MAX_THREADS, Dn as parseMessage, En as messageDeletionRefusals, I as parseThreadInput, L as parseThreadLinkInput, Mn as parseWorkSchema, N as parseThreadDeleteInput, On as parseMessageAuthor, P as parseThreadFilter, Pn as WorkValidationError, R as parseThreadReplyInput, Sn as parseWorkUpdate, Tn as messageDeletionRefusal, V as parseThreadStatusInput, a as CommentMergeError, bn as parseWorkSync, c as parseCommentInput, cn as parseSectionData, d as parseCommentThread, dn as parseWorkConnection, f as revision, fn as parseWorkCreate, gn as parseWorkMutationResult, j as parseThread, kn as withoutMessages, l as conversationId, ln as parseWorkActionInput, mn as parseWorkItem, o as mergeThreads, p as text$2, pn as parseWorkDetail, s as stableJson, u as object$1, un as parseWorkActivity, vn as parseWorkQuery, xn as parseWorkSyncResult, yn as parseWorkSnapshot, z as parseThreadSnapshot } from "./events-iHpLGr3k.js";
4
4
  import { n as PICKER_MODULE_URL, t as pickerPageScript } from "./bridge-B_Blt55E.js";
5
5
  import { createHash, randomUUID } from "node:crypto";
6
6
  import { join, relative, sep } from "node:path";
@@ -2364,6 +2364,9 @@ function beadsWorkItem(issue, schema, ready) {
2364
2364
  function taskCount(count, hidden) {
2365
2365
  return count === null ? null : Math.max(0, count - hidden);
2366
2366
  }
2367
+ let field = (value, key) => value && typeof value === "object" && !Array.isArray(value) ? value[key] : void 0;
2368
+ let deletedIds = field(field(field(issue.metadata ?? void 0, "budDiscussion"), "record"), "deleted");
2369
+ let deleted = Array.isArray(deletedIds) ? deletedIds.length : 0;
2367
2370
  let values = {
2368
2371
  ...properties,
2369
2372
  metadata: taskMetadata(issue.metadata)
@@ -2389,7 +2392,7 @@ function beadsWorkItem(issue, schema, ready) {
2389
2392
  ]) delete values[key];
2390
2393
  let counts = {};
2391
2394
  for (let [key, value] of [
2392
- ["discussion", issue.commentCount],
2395
+ ["discussion", taskCount(issue.commentCount, deleted)],
2393
2396
  ["children", issue.epicTotalChildren],
2394
2397
  ["childrenDone", issue.epicClosedChildren],
2395
2398
  ["outgoing", taskCount(issue.dependencyCount, issue.dependencies.length - dependencies.length)],
@@ -2925,6 +2928,7 @@ function parseThreadRecord(value) {
2925
2928
  "creationId",
2926
2929
  "initial",
2927
2930
  "bindings",
2931
+ "deleted",
2928
2932
  "source",
2929
2933
  "sourceId",
2930
2934
  "createdAt",
@@ -2938,6 +2942,7 @@ function parseThreadRecord(value) {
2938
2942
  let bindings = unique(unique(array(input.bindings, "bindings", binding, MAX_BEADS_THREAD_BYTES), (entry) => entry.id, "bindings.id"), (entry) => entry.nativeId, "bindings.nativeId");
2939
2943
  if (input.source === void 0 !== (input.sourceId === void 0)) throw new WorkValidationError("thread.metadata.source");
2940
2944
  if (input.source !== void 0 && (!text$1(input.source, "thread.metadata.source", 512).trim() || !text$1(input.sourceId, "thread.metadata.sourceId", 512).trim())) throw new WorkValidationError("thread.metadata.source");
2945
+ let deleted = input.deleted === void 0 ? void 0 : unique(array(input.deleted, "deleted", (id) => identifier(id, "deleted"), MAX_BEADS_THREAD_BYTES), (id) => id, "deleted");
2941
2946
  return {
2942
2947
  version: 1,
2943
2948
  kind,
@@ -2945,6 +2950,7 @@ function parseThreadRecord(value) {
2945
2950
  creationId: identifier(input.creationId, "thread.metadata.creationId"),
2946
2951
  initial,
2947
2952
  bindings,
2953
+ ...deleted?.length ? { deleted } : {},
2948
2954
  createdAt: timestamp(input.createdAt, "thread.metadata.createdAt"),
2949
2955
  updatedAt: timestamp(input.updatedAt, "thread.metadata.updatedAt"),
2950
2956
  ...input.source === void 0 ? {} : {
@@ -2954,6 +2960,14 @@ function parseThreadRecord(value) {
2954
2960
  };
2955
2961
  }
2956
2962
  /**
2963
+ * Whether a thread's first message, and with it the whole thread, was deleted.
2964
+ * @param record - The thread's Bud record.
2965
+ * @returns Whether the thread no longer shows.
2966
+ */
2967
+ function threadDeleted(record) {
2968
+ return record.deleted?.includes(record.initial.id) ?? false;
2969
+ }
2970
+ /**
2957
2971
  * Leaves unpromoted creations inspectable in Beads without listing them as durable threads.
2958
2972
  * @param value - The native issue row.
2959
2973
  * @returns A validated promoted row, or null for a pending creation.
@@ -3042,7 +3056,8 @@ function parseThreadComments(value, id) {
3042
3056
  * Validates full persisted history independently of the browser's visible message limit.
3043
3057
  * @param row - The native row and separately validated Bud metadata.
3044
3058
  * @param comments - All native messages within the process output byte limit.
3045
- * @returns Every stored message with validated attribution and complete ancestry.
3059
+ * @returns Every message not deleted, with validated attribution and complete ancestry; replies
3060
+ * to a deleted message move up to its parent.
3046
3061
  */
3047
3062
  function threadMessages(row, comments) {
3048
3063
  let bindings = new Map(row.record.bindings.map((entry) => [entry.nativeId, entry]));
@@ -3072,6 +3087,8 @@ function threadMessages(row, comments) {
3072
3087
  if (!initial || JSON.stringify(initial) !== JSON.stringify(row.record.initial)) throw new WorkValidationError("thread.messages.initial");
3073
3088
  let parents = new Map(messages.map((message) => [message.id, message.parentId]));
3074
3089
  if (parents.size !== messages.length) throw new WorkValidationError("thread.messages.id");
3090
+ let deleted = row.record.deleted ?? [];
3091
+ if (deleted.some((id) => !parents.has(id))) throw new WorkValidationError("thread.messages.deleted");
3075
3092
  let complete = /* @__PURE__ */ new Set();
3076
3093
  for (let message of messages) {
3077
3094
  let path = /* @__PURE__ */ new Set();
@@ -3083,7 +3100,7 @@ function threadMessages(row, comments) {
3083
3100
  }
3084
3101
  for (let id of path) complete.add(id);
3085
3102
  }
3086
- return messages;
3103
+ return withoutMessages(messages, deleted);
3087
3104
  }
3088
3105
  /**
3089
3106
  * Retains source order and the truthful attribution of native replies before a new append.
@@ -3117,6 +3134,8 @@ function threadView(row, comments, loaded) {
3117
3134
  let { issue, record } = row;
3118
3135
  let count = issue.commentCount;
3119
3136
  if (count === null || count < 1 || comments && comments.length !== count) throw new WorkValidationError("thread.messageCount");
3137
+ let visible = count - (record.deleted?.length ?? 0);
3138
+ if (visible < 1) throw new WorkValidationError("thread.messageCount");
3120
3139
  let all = comments ? loaded ?? threadMessages(row, comments) : [record.initial];
3121
3140
  let messages = all.slice(0, 200);
3122
3141
  let status = issue.status === "closed" ? "resolved" : issue.labels.includes("bud-status:needs_approval") ? "needs_approval" : issue.status;
@@ -3126,9 +3145,9 @@ function threadView(row, comments, loaded) {
3126
3145
  anchor: record.anchor,
3127
3146
  itemIds: linkedItems(row),
3128
3147
  messages,
3129
- messageCount: count,
3148
+ messageCount: visible,
3130
3149
  messagesLoaded: comments !== void 0,
3131
- messagesTruncated: comments !== void 0 && comments.length > 200,
3150
+ messagesTruncated: comments !== void 0 && all.length > 200,
3132
3151
  createdAt: record.createdAt,
3133
3152
  updatedAt: [
3134
3153
  record.updatedAt,
@@ -3239,6 +3258,10 @@ function readThread(session, value) {
3239
3258
  reason: "uncertain",
3240
3259
  message: "This discussion has not been promoted to durable storage. Inspect its creation in Beads before trying again."
3241
3260
  });
3261
+ if (threadDeleted(row.record)) return yield* new WorkError({
3262
+ reason: "not-found",
3263
+ message: "This discussion was deleted."
3264
+ });
3242
3265
  let comments = yield* session.comments(id, (value) => parseThreadComments(value, id));
3243
3266
  if (row.issue.commentCount !== null && comments.length !== row.issue.commentCount) return yield* new WorkError({
3244
3267
  reason: "conflict",
@@ -3368,6 +3391,20 @@ function newMessage(body, author, parentId = null) {
3368
3391
  });
3369
3392
  }
3370
3393
  /**
3394
+ * Checks that the requesting author may delete a message, against full storage.
3395
+ * @param messages - The current complete stored messages, first message first.
3396
+ * @param messageId - The message to delete.
3397
+ * @param author - The trusted server author.
3398
+ * @returns A refusal unless it is the author's own message, and a first message stands alone.
3399
+ */
3400
+ function validateDeletion(messages, messageId, author) {
3401
+ let refusal = messageDeletionRefusal(messages, messageId, author.id);
3402
+ return refusal ? fail(new WorkError({
3403
+ reason: refusal === "missing" ? "not-found" : "refused",
3404
+ message: messageDeletionRefusals[refusal]
3405
+ })) : void_;
3406
+ }
3407
+ /**
3371
3408
  * Checks reply ancestry against full storage, independent of the UI's display bound.
3372
3409
  * @param messages - The current complete stored messages.
3373
3410
  * @param parentId - The requested reply parent.
@@ -3671,6 +3708,37 @@ function replyItemDiscussion(session, id, input, author) {
3671
3708
  });
3672
3709
  }
3673
3710
  /**
3711
+ * Deletes one of the author's replies from a native task discussion; the task's first comment
3712
+ * stays. The comment stays in Beads, and the discussion record hides it.
3713
+ * @param session - The shared native writer.
3714
+ * @param id - The work-item ID, without its discussion namespace.
3715
+ * @param input - The revision-checked deletion.
3716
+ * @param author - The trusted server author.
3717
+ * @returns The changed, validated native discussion.
3718
+ */
3719
+ function deleteItemDiscussionMessage(session, id, input, author) {
3720
+ return gen(function* () {
3721
+ let current = yield* required(session, id);
3722
+ yield* expectedThread(current.thread, input.expectedRevision);
3723
+ if (input.messageId === current.data.record.initial.id) return yield* new WorkError({
3724
+ reason: "refused",
3725
+ message: "A task's first comment stays; only replies can be deleted."
3726
+ });
3727
+ yield* validateDeletion(current.messages, input.messageId, author);
3728
+ let next = {
3729
+ ...current.data,
3730
+ record: {
3731
+ ...current.data.record,
3732
+ deleted: [...current.data.record.deleted ?? [], input.messageId],
3733
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3734
+ }
3735
+ };
3736
+ let json = yield* threadInput(() => payload(current.issue, next));
3737
+ yield* threadPayload(json, session.limits.metadataBytes);
3738
+ return yield* save(session, id, json, () => map(required(session, id), (result) => result.thread));
3739
+ });
3740
+ }
3741
+ /**
3674
3742
  * Saves discussion-only status or linked items without mutating the work item's lifecycle.
3675
3743
  * @param session - The shared native writer.
3676
3744
  * @param id - The owning work item.
@@ -3847,7 +3915,7 @@ var capabilities$1 = {
3847
3915
  link: true,
3848
3916
  sync: true,
3849
3917
  edit: false,
3850
- delete: false
3918
+ delete: true
3851
3919
  };
3852
3920
  var unavailableCapabilities = {
3853
3921
  create: false,
@@ -3867,7 +3935,7 @@ function listThreads(session, filter) {
3867
3935
  }, parseThreadRows);
3868
3936
  let items = yield* listItemDiscussions(session, filter);
3869
3937
  return yield* threadResult(() => {
3870
- let threads = [...items.threads, ...rows.slice(0, MAX_THREADS).flatMap((row) => row ? [threadView(row)] : []).filter((thread) => matchesThread(thread, filter))];
3938
+ let threads = [...items.threads, ...rows.slice(0, MAX_THREADS).flatMap((row) => row && !threadDeleted(row.record) ? [threadView(row)] : []).filter((thread) => matchesThread(thread, filter))];
3871
3939
  return parseThreadSnapshot({
3872
3940
  connection: {
3873
3941
  state: "ready",
@@ -4077,6 +4145,34 @@ var beadsStoreThreadAdapterLayer = effect(ThreadAdapter, gen(function* () {
4077
4145
  }));
4078
4146
  }));
4079
4147
  }).pipe(mapError(threadFailure)),
4148
+ deleteMessage: (root, id, value, author) => gen(function* () {
4149
+ let input = yield* threadInput(() => parseThreadDeleteInput(value));
4150
+ let trusted = yield* threadInput(() => parseMessageAuthor(author));
4151
+ return yield* store.change(root, (session) => gen(function* () {
4152
+ let native = yield* threadInput(() => nativeDiscussionId(id));
4153
+ if (native) return yield* deleteItemDiscussionMessage(session, native, input, trusted);
4154
+ let { thread, row, messages } = yield* readThread(session, id);
4155
+ yield* expectedThread(thread, input.expectedRevision);
4156
+ yield* validateDeletion(messages, input.messageId, trusted);
4157
+ let record = yield* threadInput(() => parseThreadRecord({
4158
+ ...row.record,
4159
+ deleted: [...row.record.deleted ?? [], input.messageId],
4160
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4161
+ }));
4162
+ let nextMetadata = {
4163
+ ...row.issue.metadata,
4164
+ bud: record
4165
+ };
4166
+ yield* threadPayload(JSON.stringify(nextMetadata), session.limits.metadataBytes);
4167
+ return yield* threadChange(session, (writer) => gen(function* () {
4168
+ yield* saveRecord(writer, row, record);
4169
+ if (!threadDeleted(record)) return (yield* readThread(session, id)).thread;
4170
+ for (let item of thread.itemIds) yield* writer.removeDependency(id, item);
4171
+ if (thread.status !== "resolved") yield* writer.close(id, "Deleted in Bud");
4172
+ return null;
4173
+ }));
4174
+ }));
4175
+ }).pipe(mapError(threadFailure)),
4080
4176
  link: (root, id, ids, revision) => link(root, id, ids, revision, false),
4081
4177
  unlink: (root, id, ids, revision) => link(root, id, ids, revision, true),
4082
4178
  sync: (root, direction = "both") => gen(function* () {
@@ -1,9 +1,9 @@
1
- import { _ as taskRoutes, an as parseWorkDetail, cn as parseWorkMutationResult, en as parseSectionData, i as routes, pn as parseWorkSyncResult } from "./events-6z5v7Uca.js";
2
- import { Bt as Button, Dn as Text, Ft as CommentThreadDetail, It as CommentThreadItem, K as CommentThreadHeading, Lt as CommentComposer, S as ThreadTaskLinks, Tn as t, _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-B0OLHBGp.js";
3
- import { a as predictWorkAction, i as loadItemComments, n as createProjectComments, o as predictWorkCreate, s as predictWorkUpdate, t as defineScreen } from "./screen-BtcJmZwj.js";
4
- import { n as requestTasks, t as TaskRequestError } from "./tasks-request-BtJBlkxV.js";
5
- import { t as projectWork } from "./project-work-Cy_BpHwR.js";
6
- import { n as ProjectTaskPicker, t as ProjectThreadLinks } from "./project-thread-links-Nfz9NIny.js";
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
7
  import { on, ref } from "remix/component";
8
8
  import { Fragment, jsx, jsxs } from "remix/component/jsx-runtime";
9
9
  //#region app/client/task-discussion.tsx
@@ -196,7 +196,14 @@ function TaskDiscussion(handle) {
196
196
  id: thread.id,
197
197
  body,
198
198
  parentId
199
- }) : void 0
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
200
207
  })
201
208
  }, thread.id)) : /* @__PURE__ */ jsx(Text, {
202
209
  variant: "ui-caption",
@@ -1,5 +1,5 @@
1
- import { i as routes, sn as parseWorkItemRef, un as parseWorkQuery } from "./events-6z5v7Uca.js";
2
- import { Tn as t } from "./comment-data-B0OLHBGp.js";
1
+ import { hn as parseWorkItemRef, i as routes, vn as parseWorkQuery } from "./events-iHpLGr3k.js";
2
+ import { En as t } from "./comment-data-CsjukwAW.js";
3
3
  import { createMultiMatcher } from "remix/route-pattern/match";
4
4
  //#region app/client/tasks-request.ts
5
5
  var reads = createMultiMatcher();
@@ -1,7 +1,7 @@
1
1
  import { Kt as mapError, Lt as flatMap, On as try_, Ut as gen, _ as resolveStorage, f as Project, p as createProjectContext, sn as runPromise, u as Git } from "./assets/branch-previews-ClkoJ0lM.js";
2
- import { H as make, I as WorkError, O as AdapterRegistry } from "./assets/server-CYvuOSBA.js";
3
- import { j as parseThreadSnapshot, w as parseThread } from "./assets/events-6z5v7Uca.js";
4
- import { n as storageLayer, r as readGitThreadImports } from "./assets/services-DK4fqrlF.js";
2
+ import { H as make, I as WorkError, O as AdapterRegistry } from "./assets/server-DxK-UB_Z.js";
3
+ import { j as parseThread, z as parseThreadSnapshot } from "./assets/events-iHpLGr3k.js";
4
+ import { n as storageLayer, r as readGitThreadImports } from "./assets/services-BRrgXYlT.js";
5
5
  //#region app/server/comment-migration.ts
6
6
  function identity(metadata) {
7
7
  if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return null;
@@ -1,7 +1,7 @@
1
1
  import { An as uninterruptible, Dn as tryPromise, Kt as mapError, Rn as effect, Ut as gen, Zt as promise, p as createProjectContext, qn as provideMerge, sn as runPromise, sr as Service, vt as acquireRelease, x as readText } from "./assets/branch-previews-ClkoJ0lM.js";
2
- import { B as sharedModules, H as make } from "./assets/server-CYvuOSBA.js";
3
- import { n as listen, r as createApp, t as ServerError } from "./assets/listener-o1STuLju.js";
4
- import { t as applicationLayer } from "./assets/services-DK4fqrlF.js";
2
+ import { B as sharedModules, H as make } from "./assets/server-DxK-UB_Z.js";
3
+ import { n as listen, r as createApp, t as ServerError } from "./assets/listener-DN5TyOjJ.js";
4
+ import { t as applicationLayer } from "./assets/services-BRrgXYlT.js";
5
5
  import { fileURLToPath } from "node:url";
6
6
  //#region app/server/runtime.ts
7
7
  var BudListener = class extends Service()("bud/Listener") {};
@@ -1,6 +1,6 @@
1
1
  import { Kn as provide, Rn as effect, Ut as gen, b as nodeLayer, sr as Service, x as readText } from "./assets/branch-previews-ClkoJ0lM.js";
2
- import { H as make } from "./assets/server-CYvuOSBA.js";
3
- import { n as listen, r as createApp } from "./assets/listener-o1STuLju.js";
2
+ import { H as make } from "./assets/server-DxK-UB_Z.js";
3
+ import { n as listen, r as createApp } from "./assets/listener-DN5TyOjJ.js";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region server.ts
6
6
  var Listener = class extends Service()("bud/StandaloneListener") {};