@bud.sh/app 0.4.1 → 0.5.1

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-DIKX_HCJ.js → canvas-8GXJIuDK.js} +1 -1
  3. package/dist/client/assets/difference-worker-C99ychYw.js +8 -0
  4. package/dist/client/assets/{edit-B050x0rV.js → edit-DUotSxjS.js} +12 -12
  5. package/dist/client/assets/files-DkWt1l9H.js +1 -0
  6. package/dist/client/assets/i18n-hH_Ba5QO.js +1 -0
  7. package/dist/client/assets/{index-COvyj5zV.js → index-Dn2pfmXf.js} +2 -2
  8. package/dist/client/assets/index-tpHYVXma.css +2 -0
  9. package/dist/client/assets/lru-DzKYIXMA.js +1 -0
  10. package/dist/client/assets/pages-BVd6MQmu.js +1 -0
  11. package/dist/client/assets/{project-branches-DwKjCwZv.js → project-branches-CMGRmrqr.js} +1 -1
  12. package/dist/client/assets/project-comments-D1IIQwsW.js +1 -0
  13. package/dist/client/assets/project-thread-links-DKwe_SYb.js +1 -0
  14. package/dist/client/assets/{project-threads-DeKXNqeP.js → project-threads-ZbKDh78d.js} +105 -105
  15. package/dist/client/assets/{project-work-B7PGQtV9.js → project-work-D4DZqBCZ.js} +1 -1
  16. package/dist/client/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  17. package/dist/client/assets/task-design-BPpc4Gd7.js +23 -0
  18. package/dist/client/assets/tasks-Cn0VOZ9Z.js +1 -0
  19. package/dist/client/assets/{tasks-request-CsUB5__X.js → tasks-request-BLyOzokZ.js} +1 -1
  20. package/dist/client/assets/workspace-DGErsSg6.js +2 -0
  21. package/dist/client/index.html +3 -3
  22. package/dist/hosted/assets/{canvas-gWj_ocKK.js → canvas-BYTOp1p7.js} +6 -4
  23. package/dist/hosted/assets/{comment-data-C-rNCB1n.js → comment-data-Cn5y2VxV.js} +4026 -3665
  24. package/dist/hosted/assets/{files-CAPvbavY.js → files-B1P2_24q.js} +5 -3
  25. package/dist/hosted/assets/{pages-DJiOvT-l.js → pages-B0ZlVdci.js} +3 -3
  26. package/dist/hosted/assets/{project-branches-BgrfdvtL.js → project-branches-DlCWmfRo.js} +1 -1
  27. package/dist/hosted/assets/{project-thread-links-B7uzThL9.js → project-thread-links-BZXQ200n.js} +2 -2
  28. package/dist/hosted/assets/{screen-D4D9nh4V.js → screen-CfdC3NbR.js} +72 -137
  29. package/dist/hosted/assets/{tasks-C7aGLzBw.js → tasks-_yKh2Z6q.js} +12 -5
  30. package/dist/hosted/assets/{tasks-request-CikFdCgP.js → tasks-request-CFPLvLc7.js} +1 -1
  31. package/dist/hosted/hosted.js +164 -16
  32. package/dist/node/assets/{src-BdvZ2mYc.js → src-CerrKjID.js} +57 -1
  33. package/dist/node/design.js +826 -791
  34. package/dist/node/server.js +25 -3
  35. package/dist/node-host/assets/{canvas-kXzp80a5.js → canvas-DxmcFZCh.js} +6 -4
  36. package/dist/node-host/assets/{comment-data-CeuJbJKJ.js → comment-data-yfWEdDQL.js} +4004 -3643
  37. package/dist/node-host/assets/{files-0YIuvKzb.js → files-anHi-apm.js} +5 -3
  38. package/dist/node-host/assets/{pages-DOF22SyG.js → pages-CW4aF6dK.js} +3 -3
  39. package/dist/node-host/assets/{project-branches-BeEwMBIw.js → project-branches-D7rVCxQX.js} +1 -1
  40. package/dist/node-host/assets/{project-thread-links-Bs_MtfBC.js → project-thread-links-m-OLpsD9.js} +2 -2
  41. package/dist/node-host/assets/{screen-DRTAKkn3.js → screen-DapOVrga.js} +72 -137
  42. package/dist/node-host/assets/{tasks-BA_hcbj5.js → tasks-BBK_CybR.js} +12 -5
  43. package/dist/node-host/assets/{tasks-request-Doc9y_20.js → tasks-request-B0aeNocI.js} +1 -1
  44. package/dist/node-host/node-host.js +164 -16
  45. package/dist/server/assets/{canvas-CiN6jNIZ.js → canvas-CkMK4hoN.js} +7 -5
  46. package/dist/server/assets/{comment-data-9SqALQjT.js → comment-data-NLeHG89b.js} +3781 -3631
  47. package/dist/server/assets/{events-6z5v7Uca.js → events-iHpLGr3k.js} +213 -2
  48. package/dist/server/assets/{files-DBVqeLdL.js → files-CRdl489E.js} +6 -4
  49. package/dist/server/assets/{listener-DGWKUc2o.js → listener-BAYn9Q-u.js} +35 -9
  50. package/dist/server/assets/{pages-DJMLr6j9.js → pages-DXVlJlBC.js} +4 -4
  51. package/dist/server/assets/{project-branches-DbKEmiQT.js → project-branches-CwTsf6WQ.js} +2 -2
  52. package/dist/server/assets/{project-thread-links-C0kV_Pcl.js → project-thread-links-lLS_rq7c.js} +3 -3
  53. package/dist/server/assets/{project-work-C0FN3w5Y.js → project-work-CSZlCwH-.js} +3 -3
  54. package/dist/server/assets/{screen-BtvzEiIa.js → screen-B1zH_1O3.js} +73 -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-DX71x8f9.js → tasks-CGdz-uox.js} +14 -7
  58. package/dist/server/assets/{tasks-request-C3jk9Jyu.js → tasks-request-D2NBjCTl.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-CwQNxTJL.js +0 -8
  65. package/dist/client/assets/files-BQ9O3Z8-.js +0 -1
  66. package/dist/client/assets/i18n-CL0jBP7Y.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-BGl2Yuza.js +0 -1
  70. package/dist/client/assets/project-comments-Cdip2Y10.js +0 -1
  71. package/dist/client/assets/project-thread-links-BGIkGQeU.js +0 -1
  72. package/dist/client/assets/task-design-DJp5yho1.js +0 -23
  73. package/dist/client/assets/tasks-BHXj727C.js +0 -1
  74. package/dist/client/assets/workspace-CbO7RsI2.js +0 -2
@@ -1,5 +1,5 @@
1
- import { $n as parsePreviewViewport, $t as Button, Ai as fieldByRole, An as normalizeAttributeName, Bi as array$2, Bn as createMultiMatcher, Ci as parseWorkSync, Cn as composeMixedProps, Ct as legacyConversationHref, Di as valueByRole, Dn as FRAMEWORK_PROPS, Dt as taskHref, Ei as parseWorkValue, En as isEntry, Et as selectedTask, Fi as nonempty$1, Fn as Frame, G as mergeThreads, Gi as fail$11, Gn as PREVIEW_THUMBNAIL_WIDTHS, Gt as fileRoutes, Hi as createSchema, Hn as joinPatterns, Ii as parseWork, In as createComponent, J as conversationId, Ji as object$1, Jn as parsePreviewBranch, K as stableJson, Kn as PREVIEW_TILE_HEIGHT, Kt as pageRoutes, Li as record$1, Ln as createFrameHandle, Mi as statusCategory, Mn as serializeStyleObject, Ni as WorkValidationError, Nn as shouldStringifyBooleanAttribute, Oi as parseMessage, On as SELF_CLOSING_TAGS, Pi as identifier$3, Pn as Fragment, Q as text$6, Qi as string$2, Qn as parsePreviewPins, Ri as strict$2, Rn as jsx, S as writeRows, Si as parseWorkSnapshot, Sn as normalizeUnsafeHTMLProps, Ti as parseWorkUpdate, Tn as clientEntry, Tt as readConversationLocation, Ui as defaulted, Un as RoutePattern, V as parseTaskDesign, Vi as boolean$2, Vn as equal, W as CommentMergeError, Wi as enum_, Wn as MAX_PREVIEW_HEIGHT, X as parseCommentThread, Xi as parse, Xn as parsePreviewHash, Y as object$2, Yi as optional$3, Yn as parsePreviewCapture, Z as revision$2, Zi as parseSafe, Zn as parsePreviewId, Zt as Loader, _ as requestComments, _i as parseWorkDetail, an as LiveStatus, ar as parseThread, at as FieldCardHeader, b as plainRow, bi as parseWorkMutationResult, bn as stripFlushMarkers, cr as parseThreadId, ct as initialShellTabs, d as saveWorkspace, di as parseWorkWorkspace, dn as hasMessage, dr as parseThreadReplyInput, dt as readTabsCookie, er as RESYNC, fi as parseSectionData, fn as isLocale, fr as parseThreadSnapshot, ft as stepShellTab, gi as parseWorkCreate, gn as Text, gt as visitShellTab, hi as parseWorkConnection, hn as useLocaleSource, ht as tabsCookieValue, ir as parseAnchor, it as FieldCardContent, ji as parseWorkSchema, jn as sanitizeUrlAttribute, jt as legacyFileHref, ki as parseMessageAuthor, kn as isAllowedHostPropName, l as projectStore, li as MAX_CANVAS_Y, ln as readPreference, lr as parseThreadInput, lt as openShellTab, mi as parseWorkActivity, mn as t, mr as parseThreadStatusInput, mt as tabsCookieName, n as unavailableComments, nn as ShellTabBar, nr as parseWorkChange, or as parseThreadCapabilities, ot as activeTab, pi as parseWorkActionInput, pn as locale, pr as parseThreadStatus, pt as tabHref, q as parseCommentInput$1, qi as number$2, qt as taskRoutes, r as followThreads, rr as MAX_THREADS, rt as FieldCard, sn as AppShell, sr as parseThreadFilter, st as closeShellTab, t as commentsUnavailable, tr as parseLiveId, ui as parseElementAnchor, un as writePreference, ur as parseThreadLinkInput, ut as parseShellTabs, v as projectLive, vi as parseWorkItem, vn as ref$1, wi as parseWorkSyncResult, wn as resolveMixDescriptors, xi as parseWorkQuery, xn as on, y as routes, yi as parseWorkItemRef, yn as appendFlushMarker, yt as conversationDestination, zi as any, zn as Route } from "./assets/comment-data-C-rNCB1n.js";
2
- import { n as requestTasks } from "./assets/tasks-request-CikFdCgP.js";
1
+ import { $i as createSchema, $n as parsePreviewViewport, $t as Button, Ai as parseWorkSnapshot, An as normalizeAttributeName, Bi as withoutMessages, Bn as createMultiMatcher, Ci as parseWorkConnection, Cn as composeMixedProps, Ct as legacyConversationHref, Di as parseWorkItemRef, Dn as FRAMEWORK_PROPS, Dt as taskHref, Ei as parseWorkItem, En as isEntry, Et as selectedTask, Fi as valueByRole, Fn as Frame, G as mergeThreads, Gi as identifier$3, Gn as PREVIEW_THUMBNAIL_WIDTHS, Gt as fileRoutes, Hi as parseWorkSchema, Hn as joinPatterns, Ii as messageDeletionRefusal, In as createComponent, J as conversationId, Ji as record$1, Jn as parsePreviewBranch, K as stableJson, Ki as nonempty$1, Kn as PREVIEW_TILE_HEIGHT, Kt as pageRoutes, Li as messageDeletionRefusals, Ln as createFrameHandle, Mi as parseWorkSyncResult, Mn as serializeStyleObject, Ni as parseWorkUpdate, Nn as shouldStringifyBooleanAttribute, Oi as parseWorkMutationResult, On as SELF_CLOSING_TAGS, Pi as parseWorkValue, Pn as Fragment, Q as text$6, Qi as boolean$2, Qn as parsePreviewPins, Ri as parseMessage, Rn as jsx, S as writeRows, Si as parseWorkActivity, Sn as normalizeUnsafeHTMLProps, Sr as parseThreadStatusInput, Ti as parseWorkDetail, Tn as clientEntry, Tt as readConversationLocation, Ui as statusCategory, Un as RoutePattern, V as parseTaskDesign, Vi as fieldByRole, Vn as equal, W as CommentMergeError, Wi as WorkValidationError, Wn as MAX_PREVIEW_HEIGHT, X as parseCommentThread, Xi as any, Xn as parsePreviewHash, Y as object$2, Yi as strict$2, Yn as parsePreviewCapture, Z as revision$2, Zi as array$2, Zn as parsePreviewId, Zt as Loader, _ as requestComments, _i as MAX_CANVAS_Y, _r as parseThreadInput, aa as object$1, an as LiveStatus, at as FieldCardHeader, b as plainRow, bi as parseSectionData, bn as stripFlushMarkers, br as parseThreadSnapshot, ca as parseSafe, ct as initialShellTabs, d as saveWorkspace, dn as hasMessage, dr as parseAnchor, dt as readTabsCookie, ea as defaulted, er as RESYNC, fn as isLocale, fr as parseThread, ft as stepShellTab, gn as Text, gr as parseThreadId, gt as visitShellTab, hn as useLocaleSource, hr as parseThreadFilter, ht as tabsCookieValue, ia as number$2, it as FieldCardContent, ji as parseWorkSync, jn as sanitizeUrlAttribute, jt as legacyFileHref, ki as parseWorkQuery, kn as isAllowedHostPropName, l as projectStore, la as string$2, ln as readPreference, lr as parseWorkChange, lt as openShellTab, mn as t, mr as parseThreadDeleteInput, mt as tabsCookieName, n as unavailableComments, na as fail$11, nn as ShellTabBar, oa as optional$3, ot as activeTab, pn as locale, pr as parseThreadCapabilities, pt as tabHref, q as parseCommentInput$1, qi as parseWork, qt as taskRoutes, r as followThreads, rt as FieldCard, sa as parse, sn as AppShell, st as closeShellTab, t as commentsUnavailable, ta as enum_, tr as parseLiveId, un as writePreference, ur as MAX_THREADS, ut as parseShellTabs, v as projectLive, vi as parseElementAnchor, vn as ref$1, vr as parseThreadLinkInput, wi as parseWorkCreate, wn as resolveMixDescriptors, xi as parseWorkActionInput, xn as on, xr as parseThreadStatus, y as routes, yi as parseWorkWorkspace, yn as appendFlushMarker, yr as parseThreadReplyInput, yt as conversationDestination, zi as parseMessageAuthor, zn as Route } from "./assets/comment-data-Cn5y2VxV.js";
2
+ import { n as requestTasks } from "./assets/tasks-request-CFPLvLc7.js";
3
3
  import * as Crypto from "node:crypto";
4
4
  import { createHash, randomBytes, randomUUID } from "node:crypto";
5
5
  import * as Path from "node:path";
@@ -21312,6 +21312,9 @@ function beadsWorkItem(issue, schema, ready) {
21312
21312
  function taskCount(count, hidden) {
21313
21313
  return count === null ? null : Math.max(0, count - hidden);
21314
21314
  }
21315
+ let field = (value, key) => value && typeof value === "object" && !Array.isArray(value) ? value[key] : void 0;
21316
+ let deletedIds = field(field(field(issue.metadata ?? void 0, "budDiscussion"), "record"), "deleted");
21317
+ let deleted = Array.isArray(deletedIds) ? deletedIds.length : 0;
21315
21318
  let values = {
21316
21319
  ...properties,
21317
21320
  metadata: taskMetadata(issue.metadata)
@@ -21337,7 +21340,7 @@ function beadsWorkItem(issue, schema, ready) {
21337
21340
  ]) delete values[key];
21338
21341
  let counts = {};
21339
21342
  for (let [key, value] of [
21340
- ["discussion", issue.commentCount],
21343
+ ["discussion", taskCount(issue.commentCount, deleted)],
21341
21344
  ["children", issue.epicTotalChildren],
21342
21345
  ["childrenDone", issue.epicClosedChildren],
21343
21346
  ["outgoing", taskCount(issue.dependencyCount, issue.dependencies.length - dependencies.length)],
@@ -21637,10 +21640,10 @@ var MemoryStore = class {
21637
21640
  reply: this.schema.capabilities.threads,
21638
21641
  status: this.schema.capabilities.threads,
21639
21642
  link: this.schema.capabilities.threads,
21643
+ delete: this.schema.capabilities.threads,
21640
21644
  ...options.threadCapabilities,
21641
21645
  sync: false,
21642
- edit: false,
21643
- delete: false
21646
+ edit: false
21644
21647
  });
21645
21648
  if (this.schema.capabilities.sync) refused("Memory storage has no remote synchronization.");
21646
21649
  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.");
@@ -21953,6 +21956,28 @@ function memoryThreads(store) {
21953
21956
  status: request.status
21954
21957
  }, at);
21955
21958
  }),
21959
+ deleteMessage: (root, id, value, author) => store.run(root, true, (data, at) => {
21960
+ if (!store.capabilities.delete) refused("This adapter does not allow deleting messages.");
21961
+ let request = input$4(() => parseThreadDeleteInput(value));
21962
+ let trusted = input$4(() => parseMessageAuthor(author));
21963
+ let thread = store.thread(data, id);
21964
+ expected(thread, request.expectedRevision);
21965
+ let refusal = messageDeletionRefusal(thread.messages, request.messageId, trusted.id);
21966
+ if (refusal === "missing") throw new WorkError({
21967
+ reason: "not-found",
21968
+ message: messageDeletionRefusals.missing
21969
+ });
21970
+ if (refusal) refused(messageDeletionRefusals[refusal]);
21971
+ if (thread.messages[0].id === request.messageId) {
21972
+ data.threads.delete(id);
21973
+ store.touchThreads(data, thread.itemIds, at);
21974
+ return null;
21975
+ }
21976
+ return save(data, {
21977
+ ...thread,
21978
+ messages: withoutMessages(thread.messages, [request.messageId])
21979
+ }, at);
21980
+ }),
21956
21981
  link: (root, id, ids, revision) => link(root, id, ids, revision, false),
21957
21982
  unlink: (root, id, ids, revision) => link(root, id, ids, revision, true)
21958
21983
  };
@@ -22825,6 +22850,7 @@ function parseThreadRecord(value) {
22825
22850
  "creationId",
22826
22851
  "initial",
22827
22852
  "bindings",
22853
+ "deleted",
22828
22854
  "source",
22829
22855
  "sourceId",
22830
22856
  "createdAt",
@@ -22838,6 +22864,7 @@ function parseThreadRecord(value) {
22838
22864
  let bindings = unique(unique(array(input.bindings, "bindings", binding, MAX_BEADS_THREAD_BYTES), (entry) => entry.id, "bindings.id"), (entry) => entry.nativeId, "bindings.nativeId");
22839
22865
  if (input.source === void 0 !== (input.sourceId === void 0)) throw new WorkValidationError("thread.metadata.source");
22840
22866
  if (input.source !== void 0 && (!text$5(input.source, "thread.metadata.source", 512).trim() || !text$5(input.sourceId, "thread.metadata.sourceId", 512).trim())) throw new WorkValidationError("thread.metadata.source");
22867
+ let deleted = input.deleted === void 0 ? void 0 : unique(array(input.deleted, "deleted", (id) => identifier$1(id, "deleted"), MAX_BEADS_THREAD_BYTES), (id) => id, "deleted");
22841
22868
  return {
22842
22869
  version: 1,
22843
22870
  kind,
@@ -22845,6 +22872,7 @@ function parseThreadRecord(value) {
22845
22872
  creationId: identifier$1(input.creationId, "thread.metadata.creationId"),
22846
22873
  initial,
22847
22874
  bindings,
22875
+ ...deleted?.length ? { deleted } : {},
22848
22876
  createdAt: timestamp(input.createdAt, "thread.metadata.createdAt"),
22849
22877
  updatedAt: timestamp(input.updatedAt, "thread.metadata.updatedAt"),
22850
22878
  ...input.source === void 0 ? {} : {
@@ -22854,6 +22882,14 @@ function parseThreadRecord(value) {
22854
22882
  };
22855
22883
  }
22856
22884
  /**
22885
+ * Whether a thread's first message, and with it the whole thread, was deleted.
22886
+ * @param record - The thread's Bud record.
22887
+ * @returns Whether the thread no longer shows.
22888
+ */
22889
+ function threadDeleted(record) {
22890
+ return record.deleted?.includes(record.initial.id) ?? false;
22891
+ }
22892
+ /**
22857
22893
  * Leaves unpromoted creations inspectable in Beads without listing them as durable threads.
22858
22894
  * @param value - The native issue row.
22859
22895
  * @returns A validated promoted row, or null for a pending creation.
@@ -22942,7 +22978,8 @@ function parseThreadComments(value, id) {
22942
22978
  * Validates full persisted history independently of the browser's visible message limit.
22943
22979
  * @param row - The native row and separately validated Bud metadata.
22944
22980
  * @param comments - All native messages within the process output byte limit.
22945
- * @returns Every stored message with validated attribution and complete ancestry.
22981
+ * @returns Every message not deleted, with validated attribution and complete ancestry; replies
22982
+ * to a deleted message move up to its parent.
22946
22983
  */
22947
22984
  function threadMessages(row, comments) {
22948
22985
  let bindings = new Map(row.record.bindings.map((entry) => [entry.nativeId, entry]));
@@ -22972,6 +23009,8 @@ function threadMessages(row, comments) {
22972
23009
  if (!initial || JSON.stringify(initial) !== JSON.stringify(row.record.initial)) throw new WorkValidationError("thread.messages.initial");
22973
23010
  let parents = new Map(messages.map((message) => [message.id, message.parentId]));
22974
23011
  if (parents.size !== messages.length) throw new WorkValidationError("thread.messages.id");
23012
+ let deleted = row.record.deleted ?? [];
23013
+ if (deleted.some((id) => !parents.has(id))) throw new WorkValidationError("thread.messages.deleted");
22975
23014
  let complete = /* @__PURE__ */ new Set();
22976
23015
  for (let message of messages) {
22977
23016
  let path = /* @__PURE__ */ new Set();
@@ -22983,7 +23022,7 @@ function threadMessages(row, comments) {
22983
23022
  }
22984
23023
  for (let id of path) complete.add(id);
22985
23024
  }
22986
- return messages;
23025
+ return withoutMessages(messages, deleted);
22987
23026
  }
22988
23027
  /**
22989
23028
  * Retains source order and the truthful attribution of native replies before a new append.
@@ -23017,6 +23056,8 @@ function threadView(row, comments, loaded) {
23017
23056
  let { issue, record } = row;
23018
23057
  let count = issue.commentCount;
23019
23058
  if (count === null || count < 1 || comments && comments.length !== count) throw new WorkValidationError("thread.messageCount");
23059
+ let visible = count - (record.deleted?.length ?? 0);
23060
+ if (visible < 1) throw new WorkValidationError("thread.messageCount");
23020
23061
  let all = comments ? loaded ?? threadMessages(row, comments) : [record.initial];
23021
23062
  let messages = all.slice(0, 200);
23022
23063
  let status = issue.status === "closed" ? "resolved" : issue.labels.includes("bud-status:needs_approval") ? "needs_approval" : issue.status;
@@ -23026,9 +23067,9 @@ function threadView(row, comments, loaded) {
23026
23067
  anchor: record.anchor,
23027
23068
  itemIds: linkedItems(row),
23028
23069
  messages,
23029
- messageCount: count,
23070
+ messageCount: visible,
23030
23071
  messagesLoaded: comments !== void 0,
23031
- messagesTruncated: comments !== void 0 && comments.length > 200,
23072
+ messagesTruncated: comments !== void 0 && all.length > 200,
23032
23073
  createdAt: record.createdAt,
23033
23074
  updatedAt: [
23034
23075
  record.updatedAt,
@@ -23139,6 +23180,10 @@ function readThread(session, value) {
23139
23180
  reason: "uncertain",
23140
23181
  message: "This discussion has not been promoted to durable storage. Inspect its creation in Beads before trying again."
23141
23182
  });
23183
+ if (threadDeleted(row.record)) return yield* new WorkError({
23184
+ reason: "not-found",
23185
+ message: "This discussion was deleted."
23186
+ });
23142
23187
  let comments = yield* session.comments(id, (value) => parseThreadComments(value, id));
23143
23188
  if (row.issue.commentCount !== null && comments.length !== row.issue.commentCount) return yield* new WorkError({
23144
23189
  reason: "conflict",
@@ -23268,6 +23313,20 @@ function newMessage(body, author, parentId = null) {
23268
23313
  });
23269
23314
  }
23270
23315
  /**
23316
+ * Checks that the requesting author may delete a message, against full storage.
23317
+ * @param messages - The current complete stored messages, first message first.
23318
+ * @param messageId - The message to delete.
23319
+ * @param author - The trusted server author.
23320
+ * @returns A refusal unless it is the author's own message, and a first message stands alone.
23321
+ */
23322
+ function validateDeletion(messages, messageId, author) {
23323
+ let refusal = messageDeletionRefusal(messages, messageId, author.id);
23324
+ return refusal ? fail$5(new WorkError({
23325
+ reason: refusal === "missing" ? "not-found" : "refused",
23326
+ message: messageDeletionRefusals[refusal]
23327
+ })) : void_;
23328
+ }
23329
+ /**
23271
23330
  * Checks reply ancestry against full storage, independent of the UI's display bound.
23272
23331
  * @param messages - The current complete stored messages.
23273
23332
  * @param parentId - The requested reply parent.
@@ -23571,6 +23630,37 @@ function replyItemDiscussion(session, id, input, author) {
23571
23630
  });
23572
23631
  }
23573
23632
  /**
23633
+ * Deletes one of the author's replies from a native task discussion; the task's first comment
23634
+ * stays. The comment stays in Beads, and the discussion record hides it.
23635
+ * @param session - The shared native writer.
23636
+ * @param id - The work-item ID, without its discussion namespace.
23637
+ * @param input - The revision-checked deletion.
23638
+ * @param author - The trusted server author.
23639
+ * @returns The changed, validated native discussion.
23640
+ */
23641
+ function deleteItemDiscussionMessage(session, id, input, author) {
23642
+ return gen(function* () {
23643
+ let current = yield* required$1(session, id);
23644
+ yield* expectedThread(current.thread, input.expectedRevision);
23645
+ if (input.messageId === current.data.record.initial.id) return yield* new WorkError({
23646
+ reason: "refused",
23647
+ message: "A task's first comment stays; only replies can be deleted."
23648
+ });
23649
+ yield* validateDeletion(current.messages, input.messageId, author);
23650
+ let next = {
23651
+ ...current.data,
23652
+ record: {
23653
+ ...current.data.record,
23654
+ deleted: [...current.data.record.deleted ?? [], input.messageId],
23655
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
23656
+ }
23657
+ };
23658
+ let json = yield* threadInput(() => payload(current.issue, next));
23659
+ yield* threadPayload(json, session.limits.metadataBytes);
23660
+ return yield* save(session, id, json, () => map$2(required$1(session, id), (result) => result.thread));
23661
+ });
23662
+ }
23663
+ /**
23574
23664
  * Saves discussion-only status or linked items without mutating the work item's lifecycle.
23575
23665
  * @param session - The shared native writer.
23576
23666
  * @param id - The owning work item.
@@ -23747,7 +23837,7 @@ var capabilities$1 = {
23747
23837
  link: true,
23748
23838
  sync: true,
23749
23839
  edit: false,
23750
- delete: false
23840
+ delete: true
23751
23841
  };
23752
23842
  var unavailableCapabilities = {
23753
23843
  create: false,
@@ -23767,7 +23857,7 @@ function listThreads(session, filter) {
23767
23857
  }, parseThreadRows);
23768
23858
  let items = yield* listItemDiscussions(session, filter);
23769
23859
  return yield* threadResult(() => {
23770
- let threads = [...items.threads, ...rows.slice(0, MAX_THREADS).flatMap((row) => row ? [threadView(row)] : []).filter((thread) => matchesThread(thread, filter))];
23860
+ let threads = [...items.threads, ...rows.slice(0, MAX_THREADS).flatMap((row) => row && !threadDeleted(row.record) ? [threadView(row)] : []).filter((thread) => matchesThread(thread, filter))];
23771
23861
  return parseThreadSnapshot({
23772
23862
  connection: {
23773
23863
  state: "ready",
@@ -23977,6 +24067,34 @@ var beadsStoreThreadAdapterLayer = effect(ThreadAdapter, gen(function* () {
23977
24067
  }));
23978
24068
  }));
23979
24069
  }).pipe(mapError$2(threadFailure)),
24070
+ deleteMessage: (root, id, value, author) => gen(function* () {
24071
+ let input = yield* threadInput(() => parseThreadDeleteInput(value));
24072
+ let trusted = yield* threadInput(() => parseMessageAuthor(author));
24073
+ return yield* store.change(root, (session) => gen(function* () {
24074
+ let native = yield* threadInput(() => nativeDiscussionId(id));
24075
+ if (native) return yield* deleteItemDiscussionMessage(session, native, input, trusted);
24076
+ let { thread, row, messages } = yield* readThread(session, id);
24077
+ yield* expectedThread(thread, input.expectedRevision);
24078
+ yield* validateDeletion(messages, input.messageId, trusted);
24079
+ let record = yield* threadInput(() => parseThreadRecord({
24080
+ ...row.record,
24081
+ deleted: [...row.record.deleted ?? [], input.messageId],
24082
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
24083
+ }));
24084
+ let nextMetadata = {
24085
+ ...row.issue.metadata,
24086
+ bud: record
24087
+ };
24088
+ yield* threadPayload(JSON.stringify(nextMetadata), session.limits.metadataBytes);
24089
+ return yield* threadChange(session, (writer) => gen(function* () {
24090
+ yield* saveRecord(writer, row, record);
24091
+ if (!threadDeleted(record)) return (yield* readThread(session, id)).thread;
24092
+ for (let item of thread.itemIds) yield* writer.removeDependency(id, item);
24093
+ if (thread.status !== "resolved") yield* writer.close(id, "Deleted in Bud");
24094
+ return null;
24095
+ }));
24096
+ }));
24097
+ }).pipe(mapError$2(threadFailure)),
23980
24098
  link: (root, id, ids, revision) => link(root, id, ids, revision, false),
23981
24099
  unlink: (root, id, ids, revision) => link(root, id, ids, revision, true),
23982
24100
  sync: (root, direction = "both") => gen(function* () {
@@ -38766,10 +38884,10 @@ function ProjectLiveStatus(handle) {
38766
38884
  //#endregion
38767
38885
  //#region app/client/screens.ts
38768
38886
  var builtIns = {
38769
- "bud:pages": () => import("./assets/pages-DJiOvT-l.js"),
38770
- "bud:canvas": () => import("./assets/canvas-gWj_ocKK.js"),
38771
- "bud:tasks": () => import("./assets/tasks-C7aGLzBw.js"),
38772
- "bud:files": () => import("./assets/files-CAPvbavY.js")
38887
+ "bud:pages": () => import("./assets/pages-B0ZlVdci.js"),
38888
+ "bud:canvas": () => import("./assets/canvas-BYTOp1p7.js"),
38889
+ "bud:tasks": () => import("./assets/tasks-_yKh2Z6q.js"),
38890
+ "bud:files": () => import("./assets/files-B1P2_24q.js")
38773
38891
  };
38774
38892
  /**
38775
38893
  * Imports a screen module: a built-in by its key, any other by the URL the server gave it.
@@ -40618,7 +40736,11 @@ function adapterRegistryLayer(project, builtIns) {
40618
40736
  "setStatus",
40619
40737
  "link",
40620
40738
  "unlink"
40621
- ], ["sync", "import"]);
40739
+ ], [
40740
+ "sync",
40741
+ "import",
40742
+ "deleteMessage"
40743
+ ]);
40622
40744
  let connection = yield* threads.list(root, {}).pipe(flatMap$1((value) => try_({
40623
40745
  try: () => parseThreadSnapshot(value).connection,
40624
40746
  catch: failure$2(`Adapter ${selected.threads.id} returned an invalid thread connection.`)
@@ -41097,6 +41219,32 @@ function createCommentsController(project, runtime) {
41097
41219
  unlink({ request, params }) {
41098
41220
  return respond(request, (project) => written(link(request, project, params.id, true)));
41099
41221
  },
41222
+ deleteMessage({ request, params }) {
41223
+ return respond(request, (project) => written(gen(function* () {
41224
+ let id = yield* input$1(() => parseThreadId(params.id));
41225
+ let value = yield* readProjectJsonBody(request, { maxBytes: 64e3 });
41226
+ let parsed = yield* input$1(() => parseThreadDeleteInput(value));
41227
+ let store = yield* writable(project.root, "delete");
41228
+ if (!store.deleteMessage) return yield* new WorkError({
41229
+ reason: "refused",
41230
+ message: "This comment adapter does not support deleting messages."
41231
+ });
41232
+ let author = yield* commentAuthor(project.root);
41233
+ let thread = yield* store.deleteMessage(project.root, id, parsed, author);
41234
+ return yield* result(() => {
41235
+ if (thread === null) return { thread: null };
41236
+ let parsed = parseThread(thread);
41237
+ if (parsed.id !== id) throw new Error("Unexpected discussion identity.");
41238
+ return { thread: parsed };
41239
+ }, true);
41240
+ })));
41241
+ },
41242
+ viewer({ request }) {
41243
+ return respond(request, (project) => commentAuthor(project.root).pipe(map$2((author) => ({ viewer: {
41244
+ id: author.id,
41245
+ name: author.name
41246
+ } })), orElseSucceed(() => ({ viewer: null }))));
41247
+ },
41100
41248
  sync({ request }) {
41101
41249
  return respond(request, (project) => gen(function* () {
41102
41250
  let value = yield* readProjectJsonBody(request, { maxBytes: 64e3 });
@@ -484,6 +484,49 @@ var MessageSchema = field("message", strict$1({
484
484
  function parseMessageAuthor(value) {
485
485
  return parseWork(MessageAuthorSchema, value, "author");
486
486
  }
487
+ /** What each deletion refusal tells the person, safe to show. */
488
+ var messageDeletionRefusals = {
489
+ missing: "This message no longer exists.",
490
+ "not-author": "Only its author can delete a message.",
491
+ "has-replies": "The first message stays while the discussion has replies. Resolve the discussion instead."
492
+ };
493
+ /**
494
+ * Checks that someone may delete a message: only its author may, and a discussion's first message
495
+ * only while it is the only one, since deleting it removes the whole discussion.
496
+ * @param messages - The discussion's complete messages, first message first.
497
+ * @param messageId - The message to delete.
498
+ * @param authorId - Who asks, as the server derived them.
499
+ * @returns Why it can't be deleted, or null when it can.
500
+ */
501
+ function messageDeletionRefusal(messages, messageId, authorId) {
502
+ let message = messages.find((candidate) => candidate.id === messageId);
503
+ if (!message) return "missing";
504
+ if (message.author.id !== authorId) return "not-author";
505
+ if (message.id === messages[0].id && messages.length > 1) return "has-replies";
506
+ return null;
507
+ }
508
+ /**
509
+ * Leaves out deleted messages; replies to a deleted message move up to its parent.
510
+ * @param messages - The messages, in order.
511
+ * @param deleted - The IDs of the messages to leave out.
512
+ * @returns The remaining messages with complete ancestry.
513
+ */
514
+ function withoutMessages(messages, deleted) {
515
+ let gone = new Set(deleted);
516
+ if (!gone.size) return [...messages];
517
+ let parents = new Map(messages.map((message) => [message.id, message.parentId]));
518
+ let visibleParent = (id) => {
519
+ while (id !== null && gone.has(id)) id = parents.get(id) ?? null;
520
+ return id;
521
+ };
522
+ return messages.filter((message) => !gone.has(message.id)).map((message) => {
523
+ let parentId = visibleParent(message.parentId);
524
+ return parentId === message.parentId ? message : {
525
+ ...message,
526
+ parentId
527
+ };
528
+ });
529
+ }
487
530
  /**
488
531
  * Validates a message returned by storage, never a browser write request.
489
532
  * @param value - Untrusted data from a request, adapter or response.
@@ -2014,6 +2057,11 @@ var ThreadStatusInputSchema = field("status", strict$1({
2014
2057
  status: ThreadStatusSchema,
2015
2058
  expectedRevision
2016
2059
  }));
2060
+ /** A message deletion against the revision the reader saw; the server checks its author. */
2061
+ var ThreadDeleteInputSchema = field("delete", strict$1({
2062
+ messageId: identifier$1,
2063
+ expectedRevision
2064
+ }));
2017
2065
  /** A revision-checked set of work-item links. */
2018
2066
  var ThreadLinkInputSchema = field("link", verify(strict$1({
2019
2067
  itemIds,
@@ -2074,6 +2122,14 @@ function parseThreadStatusInput(value) {
2074
2122
  return parseWork(ThreadStatusInputSchema, value, "status");
2075
2123
  }
2076
2124
  /**
2125
+ * Validates a message deletion against the revision the reader saw.
2126
+ * @param value - Untrusted data from a request, adapter or response.
2127
+ * @returns The message ID and expected revision.
2128
+ */
2129
+ function parseThreadDeleteInput(value) {
2130
+ return parseWork(ThreadDeleteInputSchema, value, "delete");
2131
+ }
2132
+ /**
2077
2133
  * Validates a revision-checked set of work-item links.
2078
2134
  * @param value - Untrusted data from a request, adapter or response.
2079
2135
  * @returns The linked item IDs and expected revision.
@@ -2109,4 +2165,4 @@ function changeSchema(item) {
2109
2165
  }
2110
2166
  field("change", changeSchema(ThreadSchema));
2111
2167
  //#endregion
2112
- export { nonempty as A, parseMessage as C, statusCategory as D, parseWorkSchema as E, record$1 as M, strict$1 as N, WorkValidationError as O, valueByRole as S, fieldByRole as T, parseWorkPatch as _, parseThreadInput as a, parseWorkUpdate as b, parseThreadSnapshot as c, parseWorkActionInput as d, parseWorkCreate as f, parseWorkMutationResult as g, parseWorkItemRef as h, parseThreadFilter as i, parseWork as j, identifier$1 as k, parseThreadStatusInput as l, parseWorkItem as m, parseThread as n, parseThreadLinkInput as o, parseWorkDetail as p, parseThreadCapabilities as r, parseThreadReplyInput as s, MAX_THREADS as t, parseSectionData as u, parseWorkQuery as v, parseMessageAuthor as w, parseWorkValue as x, parseWorkSnapshot as y };
2168
+ export { parseWorkSchema as A, valueByRole as C, parseMessageAuthor as D, parseMessage as E, parseWork as F, record$1 as I, strict$1 as L, WorkValidationError as M, identifier$1 as N, withoutMessages as O, nonempty as P, parseWorkValue as S, messageDeletionRefusals as T, parseWorkMutationResult as _, parseThreadFilter as a, parseWorkSnapshot as b, parseThreadReplyInput as c, parseSectionData as d, parseWorkActionInput as f, parseWorkItemRef as g, parseWorkItem as h, parseThreadDeleteInput as i, statusCategory as j, fieldByRole as k, parseThreadSnapshot as l, parseWorkDetail as m, parseThread as n, parseThreadInput as o, parseWorkCreate as p, parseThreadCapabilities as r, parseThreadLinkInput as s, MAX_THREADS as t, parseThreadStatusInput as u, parseWorkPatch as v, messageDeletionRefusal as w, parseWorkUpdate as x, parseWorkQuery as y };