@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,5 +1,5 @@
1
- import { $r as parseWork, $t as Button, An as parseThreadStatus, Ar as parseWorkActionInput, Br as parseWorkSync, Cn as parseThreadCapabilities, Ct as legacyConversationHref, Dn as parseThreadLinkInput, Dr as parseElementAnchor, Dt as taskHref, En as parseThreadInput, Er as MAX_CANVAS_Y, Et as selectedTask, Fr as parseWorkItem, G as mergeThreads, Gr as parseMessage, Gt as fileRoutes, Hr as parseWorkUpdate, Ir as parseWorkItemRef, J as conversationId, Jr as parseWorkSchema, K as stableJson, Kr as parseMessageAuthor, Kt as pageRoutes, Lr as parseWorkMutationResult, Mr as parseWorkConnection, Nr as parseWorkCreate, On as parseThreadReplyInput, Or as parseWorkWorkspace, Pr as parseWorkDetail, Q as text$6, Qr as nonempty$1, Rr as parseWorkQuery, S as writeRows, Sn as parseThread, Tn as parseThreadId, Tt as readConversationLocation, Ur as parseWorkValue, V as parseTaskDesign, Vr as parseWorkSyncResult, W as CommentMergeError, Wr as valueByRole, X as parseCommentThread, Xr as WorkValidationError, Y as object$2, Yr as statusCategory, Z as revision$2, Zr as identifier$3, Zt as Loader, _ as requestComments, _n as RESYNC, an as LiveStatus, at as FieldCardHeader, b as plainRow, bn as MAX_THREADS, ci as parsePreviewHash, ct as initialShellTabs, d as saveWorkspace, di as parsePreviewViewport, dn as isLocale, dt as readTabsCookie, ei as record$2, fn as locale, ft as stepShellTab, gt as visitShellTab, hn as Text, ht as tabsCookieValue, ii as PREVIEW_TILE_HEIGHT, it as FieldCardContent, jn as parseThreadStatusInput, jr as parseWorkActivity, jt as legacyFileHref, kn as parseThreadSnapshot, kr as parseSectionData, l as projectStore, li as parsePreviewId, ln as readPreference, lt as openShellTab, mn as useLocaleSource, mt as tabsCookieName, n as unavailableComments, ni as MAX_PREVIEW_HEIGHT, nn as ShellTabBar, oi as parsePreviewBranch, ot as activeTab, pn as t, pt as tabHref, q as parseCommentInput$1, qr as fieldByRole, qt as taskRoutes, r as followThreads, ri as PREVIEW_THUMBNAIL_WIDTHS, rt as FieldCard, si as parsePreviewCapture, sn as AppShell, st as closeShellTab, t as commentsUnavailable, ti as strict$2, ui as parsePreviewPins, un as writePreference, ut as parseShellTabs, v as projectLive, vn as parseLiveId, wn as parseThreadFilter, xn as parseAnchor, y as routes, yn as parseWorkChange, yt as conversationDestination, zr as parseWorkSnapshot } from "./assets/comment-data-q_8INiZz.js";
2
- import { n as requestTasks } from "./assets/tasks-request-Bie1PpQP.js";
1
+ import { $r as valueByRole, $t as Button, An as parseThread, Bn as parseThreadStatusInput, Br as parseWorkActivity, Ci as parsePreviewViewport, Ct as legacyConversationHref, Dn as parseWorkChange, Dt as taskHref, Et as selectedTask, Fn as parseThreadInput, Fr as MAX_CANVAS_Y, G as mergeThreads, Gr as parseWorkItemRef, Gt as fileRoutes, Hr as parseWorkCreate, In as parseThreadLinkInput, Ir as parseElementAnchor, J as conversationId, Jr as parseWorkSnapshot, K as stableJson, Kr as parseWorkMutationResult, Kt as pageRoutes, Ln as parseThreadReplyInput, Lr as parseWorkWorkspace, Mn as parseThreadDeleteInput, Nn as parseThreadFilter, On as MAX_THREADS, Pn as parseThreadId, Q as text$6, Qr as parseWorkValue, Rn as parseThreadSnapshot, Rr as parseSectionData, S as writeRows, Si as parsePreviewPins, Tt as readConversationLocation, Ur as parseWorkDetail, V as parseTaskDesign, Vr as parseWorkConnection, W as CommentMergeError, Wr as parseWorkItem, X as parseCommentThread, Xr as parseWorkSyncResult, Y as object$2, Yr as parseWorkSync, Z as revision$2, Zr as parseWorkUpdate, Zt as Loader, _ as requestComments, ai as fieldByRole, an as LiveStatus, at as FieldCardHeader, b as plainRow, bi as parsePreviewHash, ci as WorkValidationError, ct as initialShellTabs, d as saveWorkspace, di as parseWork, dn as hasMessage, dt as readTabsCookie, ei as messageDeletionRefusal, fi as record$2, fn as isLocale, ft as stepShellTab, gi as PREVIEW_TILE_HEIGHT, gn as Text, gt as visitShellTab, hi as PREVIEW_THUMBNAIL_WIDTHS, hn as useLocaleSource, ht as tabsCookieValue, ii as withoutMessages, it as FieldCardContent, jn as parseThreadCapabilities, jt as legacyFileHref, kn as parseAnchor, l as projectStore, li as identifier$3, ln as readPreference, lt as openShellTab, mi as MAX_PREVIEW_HEIGHT, mn as t, mt as tabsCookieName, n as unavailableComments, ni as parseMessage, nn as ShellTabBar, oi as parseWorkSchema, ot as activeTab, pi as strict$2, pn as locale, pt as tabHref, q as parseCommentInput$1, qr as parseWorkQuery, qt as taskRoutes, r as followThreads, ri as parseMessageAuthor, rt as FieldCard, si as statusCategory, sn as AppShell, st as closeShellTab, t as commentsUnavailable, ti as messageDeletionRefusals, ui as nonempty$1, un as writePreference, ut as parseShellTabs, v as projectLive, vi as parsePreviewBranch, vn as RESYNC, xi as parsePreviewId, y as routes, yi as parsePreviewCapture, yn as parseLiveId, yt as conversationDestination, zn as parseThreadStatus, zr as parseWorkActionInput } from "./assets/comment-data-DyIZNpfG.js";
2
+ import { n as requestTasks } from "./assets/tasks-request-ZWmbc8fE.js";
3
3
  import { access, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
4
4
  import * as Path from "node:path";
5
5
  import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
@@ -24185,6 +24185,9 @@ function beadsWorkItem(issue, schema, ready) {
24185
24185
  function taskCount(count, hidden) {
24186
24186
  return count === null ? null : Math.max(0, count - hidden);
24187
24187
  }
24188
+ let field = (value, key) => value && typeof value === "object" && !Array.isArray(value) ? value[key] : void 0;
24189
+ let deletedIds = field(field(field(issue.metadata ?? void 0, "budDiscussion"), "record"), "deleted");
24190
+ let deleted = Array.isArray(deletedIds) ? deletedIds.length : 0;
24188
24191
  let values = {
24189
24192
  ...properties,
24190
24193
  metadata: taskMetadata(issue.metadata)
@@ -24210,7 +24213,7 @@ function beadsWorkItem(issue, schema, ready) {
24210
24213
  ]) delete values[key];
24211
24214
  let counts = {};
24212
24215
  for (let [key, value] of [
24213
- ["discussion", issue.commentCount],
24216
+ ["discussion", taskCount(issue.commentCount, deleted)],
24214
24217
  ["children", issue.epicTotalChildren],
24215
24218
  ["childrenDone", issue.epicClosedChildren],
24216
24219
  ["outgoing", taskCount(issue.dependencyCount, issue.dependencies.length - dependencies.length)],
@@ -24510,10 +24513,10 @@ var MemoryStore = class {
24510
24513
  reply: this.schema.capabilities.threads,
24511
24514
  status: this.schema.capabilities.threads,
24512
24515
  link: this.schema.capabilities.threads,
24516
+ delete: this.schema.capabilities.threads,
24513
24517
  ...options.threadCapabilities,
24514
24518
  sync: false,
24515
- edit: false,
24516
- delete: false
24519
+ edit: false
24517
24520
  });
24518
24521
  if (this.schema.capabilities.sync) refused("Memory storage has no remote synchronization.");
24519
24522
  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.");
@@ -24826,6 +24829,28 @@ function memoryThreads(store) {
24826
24829
  status: request.status
24827
24830
  }, at);
24828
24831
  }),
24832
+ deleteMessage: (root, id, value, author) => store.run(root, true, (data, at) => {
24833
+ if (!store.capabilities.delete) refused("This adapter does not allow deleting messages.");
24834
+ let request = input$4(() => parseThreadDeleteInput(value));
24835
+ let trusted = input$4(() => parseMessageAuthor(author));
24836
+ let thread = store.thread(data, id);
24837
+ expected(thread, request.expectedRevision);
24838
+ let refusal = messageDeletionRefusal(thread.messages, request.messageId, trusted.id);
24839
+ if (refusal === "missing") throw new WorkError({
24840
+ reason: "not-found",
24841
+ message: messageDeletionRefusals.missing
24842
+ });
24843
+ if (refusal) refused(messageDeletionRefusals[refusal]);
24844
+ if (thread.messages[0].id === request.messageId) {
24845
+ data.threads.delete(id);
24846
+ store.touchThreads(data, thread.itemIds, at);
24847
+ return null;
24848
+ }
24849
+ return save(data, {
24850
+ ...thread,
24851
+ messages: withoutMessages(thread.messages, [request.messageId])
24852
+ }, at);
24853
+ }),
24829
24854
  link: (root, id, ids, revision) => link(root, id, ids, revision, false),
24830
24855
  unlink: (root, id, ids, revision) => link(root, id, ids, revision, true)
24831
24856
  };
@@ -25698,6 +25723,7 @@ function parseThreadRecord(value) {
25698
25723
  "creationId",
25699
25724
  "initial",
25700
25725
  "bindings",
25726
+ "deleted",
25701
25727
  "source",
25702
25728
  "sourceId",
25703
25729
  "createdAt",
@@ -25711,6 +25737,7 @@ function parseThreadRecord(value) {
25711
25737
  let bindings = unique(unique(array$1(input.bindings, "bindings", binding, MAX_BEADS_THREAD_BYTES), (entry) => entry.id, "bindings.id"), (entry) => entry.nativeId, "bindings.nativeId");
25712
25738
  if (input.source === void 0 !== (input.sourceId === void 0)) throw new WorkValidationError("thread.metadata.source");
25713
25739
  if (input.source !== void 0 && (!text$3(input.source, "thread.metadata.source", 512).trim() || !text$3(input.sourceId, "thread.metadata.sourceId", 512).trim())) throw new WorkValidationError("thread.metadata.source");
25740
+ let deleted = input.deleted === void 0 ? void 0 : unique(array$1(input.deleted, "deleted", (id) => identifier$1(id, "deleted"), MAX_BEADS_THREAD_BYTES), (id) => id, "deleted");
25714
25741
  return {
25715
25742
  version: 1,
25716
25743
  kind,
@@ -25718,6 +25745,7 @@ function parseThreadRecord(value) {
25718
25745
  creationId: identifier$1(input.creationId, "thread.metadata.creationId"),
25719
25746
  initial,
25720
25747
  bindings,
25748
+ ...deleted?.length ? { deleted } : {},
25721
25749
  createdAt: timestamp(input.createdAt, "thread.metadata.createdAt"),
25722
25750
  updatedAt: timestamp(input.updatedAt, "thread.metadata.updatedAt"),
25723
25751
  ...input.source === void 0 ? {} : {
@@ -25727,6 +25755,14 @@ function parseThreadRecord(value) {
25727
25755
  };
25728
25756
  }
25729
25757
  /**
25758
+ * Whether a thread's first message, and with it the whole thread, was deleted.
25759
+ * @param record - The thread's Bud record.
25760
+ * @returns Whether the thread no longer shows.
25761
+ */
25762
+ function threadDeleted(record) {
25763
+ return record.deleted?.includes(record.initial.id) ?? false;
25764
+ }
25765
+ /**
25730
25766
  * Leaves unpromoted creations inspectable in Beads without listing them as durable threads.
25731
25767
  * @param value - The native issue row.
25732
25768
  * @returns A validated promoted row, or null for a pending creation.
@@ -25815,7 +25851,8 @@ function parseThreadComments(value, id) {
25815
25851
  * Validates full persisted history independently of the browser's visible message limit.
25816
25852
  * @param row - The native row and separately validated Bud metadata.
25817
25853
  * @param comments - All native messages within the process output byte limit.
25818
- * @returns Every stored message with validated attribution and complete ancestry.
25854
+ * @returns Every message not deleted, with validated attribution and complete ancestry; replies
25855
+ * to a deleted message move up to its parent.
25819
25856
  */
25820
25857
  function threadMessages(row, comments) {
25821
25858
  let bindings = new Map(row.record.bindings.map((entry) => [entry.nativeId, entry]));
@@ -25845,6 +25882,8 @@ function threadMessages(row, comments) {
25845
25882
  if (!initial || JSON.stringify(initial) !== JSON.stringify(row.record.initial)) throw new WorkValidationError("thread.messages.initial");
25846
25883
  let parents = new Map(messages.map((message) => [message.id, message.parentId]));
25847
25884
  if (parents.size !== messages.length) throw new WorkValidationError("thread.messages.id");
25885
+ let deleted = row.record.deleted ?? [];
25886
+ if (deleted.some((id) => !parents.has(id))) throw new WorkValidationError("thread.messages.deleted");
25848
25887
  let complete = /* @__PURE__ */ new Set();
25849
25888
  for (let message of messages) {
25850
25889
  let path = /* @__PURE__ */ new Set();
@@ -25856,7 +25895,7 @@ function threadMessages(row, comments) {
25856
25895
  }
25857
25896
  for (let id of path) complete.add(id);
25858
25897
  }
25859
- return messages;
25898
+ return withoutMessages(messages, deleted);
25860
25899
  }
25861
25900
  /**
25862
25901
  * Retains source order and the truthful attribution of native replies before a new append.
@@ -25890,6 +25929,8 @@ function threadView(row, comments, loaded) {
25890
25929
  let { issue, record } = row;
25891
25930
  let count = issue.commentCount;
25892
25931
  if (count === null || count < 1 || comments && comments.length !== count) throw new WorkValidationError("thread.messageCount");
25932
+ let visible = count - (record.deleted?.length ?? 0);
25933
+ if (visible < 1) throw new WorkValidationError("thread.messageCount");
25893
25934
  let all = comments ? loaded ?? threadMessages(row, comments) : [record.initial];
25894
25935
  let messages = all.slice(0, 200);
25895
25936
  let status = issue.status === "closed" ? "resolved" : issue.labels.includes("bud-status:needs_approval") ? "needs_approval" : issue.status;
@@ -25899,9 +25940,9 @@ function threadView(row, comments, loaded) {
25899
25940
  anchor: record.anchor,
25900
25941
  itemIds: linkedItems(row),
25901
25942
  messages,
25902
- messageCount: count,
25943
+ messageCount: visible,
25903
25944
  messagesLoaded: comments !== void 0,
25904
- messagesTruncated: comments !== void 0 && comments.length > 200,
25945
+ messagesTruncated: comments !== void 0 && all.length > 200,
25905
25946
  createdAt: record.createdAt,
25906
25947
  updatedAt: [
25907
25948
  record.updatedAt,
@@ -26012,6 +26053,10 @@ function readThread(session, value) {
26012
26053
  reason: "uncertain",
26013
26054
  message: "This discussion has not been promoted to durable storage. Inspect its creation in Beads before trying again."
26014
26055
  });
26056
+ if (threadDeleted(row.record)) return yield* new WorkError({
26057
+ reason: "not-found",
26058
+ message: "This discussion was deleted."
26059
+ });
26015
26060
  let comments = yield* session.comments(id, (value) => parseThreadComments(value, id));
26016
26061
  if (row.issue.commentCount !== null && comments.length !== row.issue.commentCount) return yield* new WorkError({
26017
26062
  reason: "conflict",
@@ -26141,6 +26186,20 @@ function newMessage(body, author, parentId = null) {
26141
26186
  });
26142
26187
  }
26143
26188
  /**
26189
+ * Checks that the requesting author may delete a message, against full storage.
26190
+ * @param messages - The current complete stored messages, first message first.
26191
+ * @param messageId - The message to delete.
26192
+ * @param author - The trusted server author.
26193
+ * @returns A refusal unless it is the author's own message, and a first message stands alone.
26194
+ */
26195
+ function validateDeletion(messages, messageId, author) {
26196
+ let refusal = messageDeletionRefusal(messages, messageId, author.id);
26197
+ return refusal ? fail$6(new WorkError({
26198
+ reason: refusal === "missing" ? "not-found" : "refused",
26199
+ message: messageDeletionRefusals[refusal]
26200
+ })) : void_;
26201
+ }
26202
+ /**
26144
26203
  * Checks reply ancestry against full storage, independent of the UI's display bound.
26145
26204
  * @param messages - The current complete stored messages.
26146
26205
  * @param parentId - The requested reply parent.
@@ -26444,6 +26503,37 @@ function replyItemDiscussion(session, id, input, author) {
26444
26503
  });
26445
26504
  }
26446
26505
  /**
26506
+ * Deletes one of the author's replies from a native task discussion; the task's first comment
26507
+ * stays. The comment stays in Beads, and the discussion record hides it.
26508
+ * @param session - The shared native writer.
26509
+ * @param id - The work-item ID, without its discussion namespace.
26510
+ * @param input - The revision-checked deletion.
26511
+ * @param author - The trusted server author.
26512
+ * @returns The changed, validated native discussion.
26513
+ */
26514
+ function deleteItemDiscussionMessage(session, id, input, author) {
26515
+ return gen(function* () {
26516
+ let current = yield* required$1(session, id);
26517
+ yield* expectedThread(current.thread, input.expectedRevision);
26518
+ if (input.messageId === current.data.record.initial.id) return yield* new WorkError({
26519
+ reason: "refused",
26520
+ message: "A task's first comment stays; only replies can be deleted."
26521
+ });
26522
+ yield* validateDeletion(current.messages, input.messageId, author);
26523
+ let next = {
26524
+ ...current.data,
26525
+ record: {
26526
+ ...current.data.record,
26527
+ deleted: [...current.data.record.deleted ?? [], input.messageId],
26528
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
26529
+ }
26530
+ };
26531
+ let json = yield* threadInput(() => payload(current.issue, next));
26532
+ yield* threadPayload(json, session.limits.metadataBytes);
26533
+ return yield* save(session, id, json, () => map$2(required$1(session, id), (result) => result.thread));
26534
+ });
26535
+ }
26536
+ /**
26447
26537
  * Saves discussion-only status or linked items without mutating the work item's lifecycle.
26448
26538
  * @param session - The shared native writer.
26449
26539
  * @param id - The owning work item.
@@ -26620,7 +26710,7 @@ var capabilities$1 = {
26620
26710
  link: true,
26621
26711
  sync: true,
26622
26712
  edit: false,
26623
- delete: false
26713
+ delete: true
26624
26714
  };
26625
26715
  var unavailableCapabilities = {
26626
26716
  create: false,
@@ -26640,7 +26730,7 @@ function listThreads(session, filter) {
26640
26730
  }, parseThreadRows);
26641
26731
  let items = yield* listItemDiscussions(session, filter);
26642
26732
  return yield* threadResult(() => {
26643
- let threads = [...items.threads, ...rows.slice(0, MAX_THREADS).flatMap((row) => row ? [threadView(row)] : []).filter((thread) => matchesThread(thread, filter))];
26733
+ let threads = [...items.threads, ...rows.slice(0, MAX_THREADS).flatMap((row) => row && !threadDeleted(row.record) ? [threadView(row)] : []).filter((thread) => matchesThread(thread, filter))];
26644
26734
  return parseThreadSnapshot({
26645
26735
  connection: {
26646
26736
  state: "ready",
@@ -26850,6 +26940,34 @@ var beadsStoreThreadAdapterLayer = effect(ThreadAdapter, gen(function* () {
26850
26940
  }));
26851
26941
  }));
26852
26942
  }).pipe(mapError$2(threadFailure)),
26943
+ deleteMessage: (root, id, value, author) => gen(function* () {
26944
+ let input = yield* threadInput(() => parseThreadDeleteInput(value));
26945
+ let trusted = yield* threadInput(() => parseMessageAuthor(author));
26946
+ return yield* store.change(root, (session) => gen(function* () {
26947
+ let native = yield* threadInput(() => nativeDiscussionId(id));
26948
+ if (native) return yield* deleteItemDiscussionMessage(session, native, input, trusted);
26949
+ let { thread, row, messages } = yield* readThread(session, id);
26950
+ yield* expectedThread(thread, input.expectedRevision);
26951
+ yield* validateDeletion(messages, input.messageId, trusted);
26952
+ let record = yield* threadInput(() => parseThreadRecord({
26953
+ ...row.record,
26954
+ deleted: [...row.record.deleted ?? [], input.messageId],
26955
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
26956
+ }));
26957
+ let nextMetadata = {
26958
+ ...row.issue.metadata,
26959
+ bud: record
26960
+ };
26961
+ yield* threadPayload(JSON.stringify(nextMetadata), session.limits.metadataBytes);
26962
+ return yield* threadChange(session, (writer) => gen(function* () {
26963
+ yield* saveRecord(writer, row, record);
26964
+ if (!threadDeleted(record)) return (yield* readThread(session, id)).thread;
26965
+ for (let item of thread.itemIds) yield* writer.removeDependency(id, item);
26966
+ if (thread.status !== "resolved") yield* writer.close(id, "Deleted in Bud");
26967
+ return null;
26968
+ }));
26969
+ }));
26970
+ }).pipe(mapError$2(threadFailure)),
26853
26971
  link: (root, id, ids, revision) => link(root, id, ids, revision, false),
26854
26972
  unlink: (root, id, ids, revision) => link(root, id, ids, revision, true),
26855
26973
  sync: (root, direction = "both") => gen(function* () {
@@ -37473,10 +37591,10 @@ function ProjectLiveStatus(handle) {
37473
37591
  //#endregion
37474
37592
  //#region app/client/screens.ts
37475
37593
  var builtIns = {
37476
- "bud:pages": () => import("./assets/pages-D6VkmdfQ.js"),
37477
- "bud:canvas": () => import("./assets/canvas-D_F8SIlm.js"),
37478
- "bud:tasks": () => import("./assets/tasks-A1_bpb3_.js"),
37479
- "bud:files": () => import("./assets/files-xazOrNCX.js")
37594
+ "bud:pages": () => import("./assets/pages-BxpjbYhs.js"),
37595
+ "bud:canvas": () => import("./assets/canvas-TRUfxfL5.js"),
37596
+ "bud:tasks": () => import("./assets/tasks-Bs1IOpeU.js"),
37597
+ "bud:files": () => import("./assets/files-Dh2TiQZq.js")
37480
37598
  };
37481
37599
  /**
37482
37600
  * Imports a screen module: a built-in by its key, any other by the URL the server gave it.
@@ -38049,6 +38167,11 @@ var builtInRoutes = {
38049
38167
  "bud:tasks": taskRoutes,
38050
38168
  "bud:files": fileRoutes
38051
38169
  };
38170
+ function screenName(screen) {
38171
+ let builtIn = defaultScreens.find((candidate) => candidate.module === screen.module);
38172
+ let key = `screens.${builtIn?.id}`;
38173
+ return builtIn && screen.label === builtIn.label && hasMessage(key) ? t(key) : screen.label;
38174
+ }
38052
38175
  /**
38053
38176
  * Connects the route contract to application responses.
38054
38177
  * @param template - The development or production HTML shell.
@@ -38089,7 +38212,10 @@ function createAppController(template, project, runtime) {
38089
38212
  let module = await prepare(screen, request.signal);
38090
38213
  let taskDesign = project && (screen.module === "bud:tasks" || conversation !== void 0 && screens.some((target) => target.module === "bud:tasks")) && project.config.designs.tasks !== "bud:beads" && runtime ? await runtime.runPromise(ScreenModules.use((modules) => modules.module(taskDesignModuleId)), { signal: request.signal }) : void 0;
38091
38214
  let content = await renderToString(/* @__PURE__ */ jsx(WorkspacePage, {
38092
- screens: items,
38215
+ screens: items.map((item, index) => ({
38216
+ ...item,
38217
+ label: screenName(screens[index])
38218
+ })),
38093
38219
  active: screen.id,
38094
38220
  module,
38095
38221
  taskDesign,
@@ -38116,7 +38242,7 @@ function createAppController(template, project, runtime) {
38116
38242
  ...patterns ? { routes: patterns } : {}
38117
38243
  }
38118
38244
  }));
38119
- let title = (conversation ? "Discussion" : screen.label).replace(/[&<>]/g, (character) => `&#${character.charCodeAt(0)};`);
38245
+ let title = (conversation ? t("app.controller.discussion") : screenName(screen)).replace(/[&<>]/g, (character) => `&#${character.charCodeAt(0)};`);
38120
38246
  let logo = project?.config.logo;
38121
38247
  let shell = logo ? withLogoIcons(template, logo, (theme) => routes.logo.href({ theme })) : template;
38122
38248
  return new Response(shell.replace("<html lang=\"en\"", () => `<html lang="${locale()}"`).replace("<title>bud.sh</title>", () => `<title>${title} · bud.sh</title>`).replace("</head>", () => project ? `${themeStyle(project.config.theme)}</head>` : "</head>").replace("<!--app-html-->", () => content), { headers: { "Content-Type": "text/html; charset=utf-8" } });
@@ -39317,7 +39443,11 @@ function adapterRegistryLayer(project, builtIns) {
39317
39443
  "setStatus",
39318
39444
  "link",
39319
39445
  "unlink"
39320
- ], ["sync", "import"]);
39446
+ ], [
39447
+ "sync",
39448
+ "import",
39449
+ "deleteMessage"
39450
+ ]);
39321
39451
  let connection = yield* threads.list(root, {}).pipe(flatMap$1((value) => try_({
39322
39452
  try: () => parseThreadSnapshot(value).connection,
39323
39453
  catch: failure$2(`Adapter ${selected.threads.id} returned an invalid thread connection.`)
@@ -39796,6 +39926,32 @@ function createCommentsController(project, runtime) {
39796
39926
  unlink({ request, params }) {
39797
39927
  return respond(request, (project) => written(link(request, project, params.id, true)));
39798
39928
  },
39929
+ deleteMessage({ request, params }) {
39930
+ return respond(request, (project) => written(gen(function* () {
39931
+ let id = yield* input$1(() => parseThreadId(params.id));
39932
+ let value = yield* readProjectJsonBody(request, { maxBytes: 64e3 });
39933
+ let parsed = yield* input$1(() => parseThreadDeleteInput(value));
39934
+ let store = yield* writable(project.root, "delete");
39935
+ if (!store.deleteMessage) return yield* new WorkError({
39936
+ reason: "refused",
39937
+ message: "This comment adapter does not support deleting messages."
39938
+ });
39939
+ let author = yield* commentAuthor(project.root);
39940
+ let thread = yield* store.deleteMessage(project.root, id, parsed, author);
39941
+ return yield* result(() => {
39942
+ if (thread === null) return { thread: null };
39943
+ let parsed = parseThread(thread);
39944
+ if (parsed.id !== id) throw new Error("Unexpected discussion identity.");
39945
+ return { thread: parsed };
39946
+ }, true);
39947
+ })));
39948
+ },
39949
+ viewer({ request }) {
39950
+ return respond(request, (project) => commentAuthor(project.root).pipe(map$2((author) => ({ viewer: {
39951
+ id: author.id,
39952
+ name: author.name
39953
+ } })), orElseSucceed(() => ({ viewer: null }))));
39954
+ },
39799
39955
  sync({ request }) {
39800
39956
  return respond(request, (project) => gen(function* () {
39801
39957
  let value = yield* readProjectJsonBody(request, { maxBytes: 64e3 });
@@ -1,8 +1,8 @@
1
- import { kn as Icon } from "./events-6z5v7Uca.js";
2
- import { Bt as Button, Dn as Text, H as captureCanvasPoint, Rt as Loader, Sn as writePreference, Tn as t, V as CanvasWorkspace, W as BranchSidebar, fn as ToolbarSeparator, gn as IconButton, hn as Select, l as projectStore, pn as WorkspaceLayout, t as commentsUnavailable, ut as conversationHref, vn as Tooltip, xn as readPreference } from "./comment-data-B0OLHBGp.js";
3
- import { n as createProjectComments, r as loadComments, t as defineScreen } from "./screen-BtcJmZwj.js";
4
- import { i as pictureKey, n as createProjectPreviews, t as createProjectBranches } from "./project-branches-GtQYyOH5.js";
5
- import { t as ProjectThreadLinks } from "./project-thread-links-Nfz9NIny.js";
1
+ import { Bn as Icon } from "./events-iHpLGr3k.js";
2
+ import { Bt as Button, En as t, H as captureCanvasPoint, On as Text, Rt as Loader, Sn as writePreference, V as CanvasWorkspace, W as BranchSidebar, fn as ToolbarSeparator, gn as IconButton, hn as Select, l as projectStore, pn as WorkspaceLayout, t as commentsUnavailable, ut as conversationHref, vn as Tooltip, xn as readPreference } from "./comment-data-CsjukwAW.js";
3
+ import { i as pictureKey, n as createProjectPreviews, t as createProjectBranches } from "./project-branches-B0YIK6Ur.js";
4
+ import { n as createProjectComments, r as loadComments, t as defineScreen } from "./screen-BIQA2mr4.js";
5
+ import { t as ProjectThreadLinks } from "./project-thread-links-CBOpf6zk.js";
6
6
  import { on, ref } from "remix/component";
7
7
  import { Fragment, jsx, jsxs } from "remix/component/jsx-runtime";
8
8
  //#region app/client/preview-image.tsx
@@ -1034,6 +1034,8 @@ function ProjectCanvas(handle) {
1034
1034
  },
1035
1035
  onSync: () => comments.sync().catch(() => {}),
1036
1036
  onReload: comments.load,
1037
+ viewerId: comments.viewer?.id,
1038
+ onDeleteComment: (threadId, messageId) => comments.remove(threadId, messageId),
1037
1039
  onCopyThreadLink: (thread) => navigator.clipboard.writeText(new URL(conversationHref(thread.id), window.location.origin).href),
1038
1040
  interactive: false,
1039
1041
  branch: selected,