@bud.sh/app 0.2.0 → 0.4.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 (77) hide show
  1. package/dist/client/assets/bud/design.js +1 -1
  2. package/dist/client/assets/canvas-C8hyPWoT.js +1 -0
  3. package/dist/client/assets/difference-worker-BfF4_N3e.js +8 -0
  4. package/dist/client/assets/files-D_Tm7gCK.js +1 -0
  5. package/dist/client/assets/i18n-BtfXum8P.js +1 -0
  6. package/dist/client/assets/index-Dpxjiok1.js +2 -0
  7. package/dist/client/assets/pages-B-_CKvDS.js +1 -0
  8. package/dist/client/assets/project-branches-DzXdaT9L.js +1 -0
  9. package/dist/client/assets/project-comments-CQkB2jbE.js +1 -0
  10. package/dist/client/assets/project-thread-links-BN7zL0AX.js +1 -0
  11. package/dist/client/assets/{project-threads-DezXiVK6.js → project-threads-CHGG-6fo.js} +104 -104
  12. package/dist/client/assets/{project-work-CB1JHqGW.js → project-work-L5fYMSOu.js} +1 -1
  13. package/dist/client/assets/task-design-DYqcqq1b.js +23 -0
  14. package/dist/client/assets/tasks-A2FKo3_h.js +1 -0
  15. package/dist/client/assets/tasks-request-DGigNdyK.js +1 -0
  16. package/dist/client/assets/workspace-CvWmU2RK.js +2 -0
  17. package/dist/client/index.html +2 -1
  18. package/dist/hosted/assets/{canvas-C69-96GR.js → canvas-MWtOwaWm.js} +69 -41
  19. package/dist/hosted/assets/{comment-data-CiDuvXi2.js → comment-data-DsgJWq4n.js} +7195 -1067
  20. package/dist/hosted/assets/{files-DQL5hzs3.js → files-Wvo11G2A.js} +92 -89
  21. package/dist/hosted/assets/{pages-DFs7jB6M.js → pages-BAmRFVtU.js} +31 -22
  22. package/dist/hosted/assets/{project-branches-CffV4IS1.js → project-branches-BNloVosX.js} +5 -5
  23. package/dist/hosted/assets/{project-thread-links-B1Y2bDUx.js → project-thread-links-BmY8HQXt.js} +12 -12
  24. package/dist/hosted/assets/{screen-BroBb1Z_.js → screen-QmqmI2Aq.js} +18 -18
  25. package/dist/hosted/assets/{tasks-mzmNhe2d.js → tasks-C8iWPrWg.js} +29 -29
  26. package/dist/hosted/assets/{tasks-request-Dcrpd2_8.js → tasks-request-C89fN1dK.js} +7 -7
  27. package/dist/hosted/hosted.js +159 -31
  28. package/dist/node/design.js +6235 -139
  29. package/dist/node/index.js +99 -2
  30. package/dist/node-host/assets/{canvas-BZhzizzt.js → canvas-D_F8SIlm.js} +70 -42
  31. package/dist/node-host/assets/{comment-data-nUlI0szN.js → comment-data-q_8INiZz.js} +7221 -1093
  32. package/dist/node-host/assets/{files-CTzcWGjb.js → files-xazOrNCX.js} +92 -89
  33. package/dist/node-host/assets/{pages-0aWH7z1j.js → pages-D6VkmdfQ.js} +31 -22
  34. package/dist/node-host/assets/{project-branches-1zs_0J_y.js → project-branches-C26hwmo3.js} +5 -5
  35. package/dist/node-host/assets/{project-thread-links-LpUAGcDr.js → project-thread-links-BOzjws5W.js} +12 -12
  36. package/dist/node-host/assets/{screen-DQoTOWYR.js → screen-Dog-YByu.js} +18 -18
  37. package/dist/node-host/assets/{tasks-C0WqnM7Q.js → tasks-A1_bpb3_.js} +30 -30
  38. package/dist/node-host/assets/{tasks-request-Bwky5ORf.js → tasks-request-Bie1PpQP.js} +7 -7
  39. package/dist/node-host/node-host.js +1765 -1630
  40. package/dist/server/assets/{branch-previews-C_TVt_HQ.js → branch-previews-ClkoJ0lM.js} +116 -5
  41. package/dist/server/assets/{canvas-D6tIkONI.js → canvas-Dn4RYoU2.js} +70 -42
  42. package/dist/server/assets/{comment-data-BuArfgcc.js → comment-data-B0OLHBGp.js} +7219 -1091
  43. package/dist/server/assets/{files-DkrEB48U.js → files-C5Vilfe4.js} +92 -89
  44. package/dist/server/assets/{listener-BVbFsnmX.js → listener-o1STuLju.js} +43 -29
  45. package/dist/server/assets/{pages-iEdmK_o2.js → pages-BNa0MHEc.js} +31 -22
  46. package/dist/server/assets/{project-branches-B7rtrvMl.js → project-branches-GtQYyOH5.js} +5 -5
  47. package/dist/server/assets/{project-thread-links-ue_ad9IX.js → project-thread-links-Nfz9NIny.js} +12 -12
  48. package/dist/server/assets/{project-work-6aOyja4v.js → project-work-Cy_BpHwR.js} +2 -2
  49. package/dist/server/assets/{screen-DMUB9a-K.js → screen-BtcJmZwj.js} +18 -18
  50. package/dist/server/assets/{server-DUBjmaBw.js → server-CYvuOSBA.js} +1 -1
  51. package/dist/server/assets/{services-Og5rcF50.js → services-DK4fqrlF.js} +2 -2
  52. package/dist/server/assets/{tasks-C2oa2wSC.js → tasks-B82tHwyQ.js} +31 -31
  53. package/dist/server/assets/{tasks-request-Bk5EoY_p.js → tasks-request-BtJBlkxV.js} +7 -6
  54. package/dist/server/capture.js +1 -1
  55. package/dist/server/migration.js +3 -3
  56. package/dist/server/runtime.js +4 -4
  57. package/dist/server/server.js +3 -3
  58. package/dist/types/assets/{config-BzzRLcL-.d.ts → config-DIC2cf_S.d.ts} +27 -1
  59. package/dist/types/assets/{contract-jA5pqK6h.d.ts → contract-CAT-XFNq.d.ts} +3 -1
  60. package/dist/types/companion.d.ts +1 -1
  61. package/dist/types/hosted.d.ts +2 -2
  62. package/dist/types/index.d.ts +1 -1
  63. package/dist/types/node-host.d.ts +7 -1
  64. package/dist/types/screen.d.ts +1 -1
  65. package/package.json +1 -1
  66. package/dist/client/assets/canvas-DxaIItAs.js +0 -1
  67. package/dist/client/assets/difference-worker-BpqPBs5g.js +0 -8
  68. package/dist/client/assets/files-C-7xoSt4.js +0 -1
  69. package/dist/client/assets/index-sj6mWw9-.js +0 -2
  70. package/dist/client/assets/pages-BVrTX_Eq.js +0 -1
  71. package/dist/client/assets/project-branches-DpxQx-T4.js +0 -1
  72. package/dist/client/assets/project-comments-D897aU5O.js +0 -1
  73. package/dist/client/assets/project-thread-links-Bp_TseD0.js +0 -1
  74. package/dist/client/assets/task-design-bktfn_Ok.js +0 -23
  75. package/dist/client/assets/tasks-Cqfj4fQN.js +0 -1
  76. package/dist/client/assets/tasks-request-mWMk9pBo.js +0 -1
  77. package/dist/client/assets/workspace-9KxFcfyV.js +0 -2
@@ -1,7 +1,7 @@
1
- import { Fi as array, Hi as number, Ki as parseSafe, Kt as pageRoutes, Nn as jsx, Qt as Spinner, Ui as object, Wi as optional, Zt as Loader, _t as PagesWorkspace, dn as Text, gn as on, ii as MAX_CANVAS_Y, in as IconButton, kn as Fragment, l as projectStore, ln as readPreference, pn as ref, qi as string, rn as Select, un as writePreference } from "./comment-data-CiDuvXi2.js";
2
- import { n as createProjectComments, t as defineScreen } from "./screen-BroBb1Z_.js";
1
+ import { Ji as optional, Ki as number, Kt as pageRoutes, Ln as jsx, Nn as Fragment, Qt as Spinner, Xi as parseSafe, Zi as string, Zt as Loader, _n as ref, _t as PagesWorkspace, bn as on, ci as MAX_CANVAS_Y, hn as Text, in as IconButton, l as projectStore, ln as readPreference, pn as t, qi as object, rn as Select, un as writePreference, zi as array } from "./comment-data-DsgJWq4n.js";
2
+ import { n as createProjectComments, t as defineScreen } from "./screen-QmqmI2Aq.js";
3
3
  import { n as workspaceTab, t as followWorkspaceTab } from "../hosted.js";
4
- import { n as createProjectPreviews, r as locatePreview, t as createProjectBranches } from "./project-branches-CffV4IS1.js";
4
+ import { n as createProjectPreviews, r as locatePreview, t as createProjectBranches } from "./project-branches-BNloVosX.js";
5
5
  //#region app/client/page-picture.tsx
6
6
  /**
7
7
  * A page as its latest captured picture, for Pages where there is no running site: its slices
@@ -40,7 +40,10 @@ function PagePicture(handle) {
40
40
  return /* @__PURE__ */ jsx("div", {
41
41
  class: inline ? "relative w-full" : "absolute inset-0 overflow-auto bg-board",
42
42
  role: "img",
43
- "aria-label": `${route.title} picture at ${size.width} pixels`,
43
+ "aria-label": t("app.client.pagePicture.pictureAt", {
44
+ title: route.title,
45
+ width: size.width
46
+ }),
44
47
  children: current ? /* @__PURE__ */ jsx("div", {
45
48
  class: `relative mx-auto ${picking ? "cursor-crosshair" : ""}`,
46
49
  style: { width: `min(100%, ${current.capture.width}px)` },
@@ -76,7 +79,7 @@ function PagePicture(handle) {
76
79
  variant: "ui",
77
80
  tone: "muted",
78
81
  class: "max-w-md text-center",
79
- children: "This page has no picture at this viewport yet. It appears once its branch is captured."
82
+ children: t("app.client.pagePicture.noPictureYet")
80
83
  }) : /* @__PURE__ */ jsx(Spinner, {
81
84
  size: 18,
82
85
  class: "text-muted"
@@ -188,9 +191,12 @@ function ProjectPages(handle) {
188
191
  }] : []);
189
192
  return pictureMode ? pictured : [{
190
193
  name: workingTree,
191
- caption: "live"
194
+ caption: t("app.client.projectPages.live")
192
195
  }, ...pictured];
193
196
  }
197
+ function branchLabel(name) {
198
+ return name === workingTree ? t("app.client.projectPages.workingTree") : name;
199
+ }
194
200
  let comparing = null;
195
201
  let pickedFrom;
196
202
  let viewport = project.previews.viewports[0];
@@ -325,26 +331,26 @@ function ProjectPages(handle) {
325
331
  if (!pictureMode && options.length < 2 && !project.routes.some((route) => route.flow)) return void 0;
326
332
  let compared = comparing;
327
333
  return {
328
- source: compared ? `Comparing ${compared.join(", ")}` : `Pictures of ${activeTab().branch ?? home}`,
334
+ source: compared ? t("app.client.projectPages.comparing", { branches: compared.join(", ") }) : t("app.client.projectPages.picturesOf", { branch: branchLabel(activeTab().branch ?? home) }),
329
335
  shows: (tab) => pictureMode || Boolean(tab.branch) || Boolean(compared) || Boolean(pageRoute(project.routes, tab.path)?.flow),
330
336
  actions: /* @__PURE__ */ jsx(Fragment, { children: [/* @__PURE__ */ jsx(IconButton, {
331
337
  tooltip: true,
332
338
  icon: "compare",
333
- label: compared ? "Stop comparing" : "Compare branches side by side",
339
+ label: compared ? t("app.client.projectPages.stopComparing") : t("app.client.projectPages.compareBranches"),
334
340
  active: Boolean(compared),
335
341
  disabled: !compared && options.length < 2,
336
342
  mix: on("click", toggleComparing)
337
343
  }), compared ? /* @__PURE__ */ jsx("div", {
338
344
  role: "group",
339
- "aria-label": "Branches compared",
345
+ "aria-label": t("app.client.projectPages.branchesCompared"),
340
346
  class: "flex min-w-0 items-center gap-1.5",
341
347
  children: compared.map((name, index) => /* @__PURE__ */ jsx(Select, {
342
- label: `Compared branch ${index + 1}`,
348
+ label: t("app.client.projectPages.comparedBranch", { number: index + 1 }),
343
349
  value: name,
344
350
  class: "max-w-52",
345
351
  options: options.map((option) => ({
346
352
  value: option.name,
347
- label: option.name,
353
+ label: branchLabel(option.name),
348
354
  description: option.caption
349
355
  })),
350
356
  onChange: (value) => {
@@ -360,7 +366,10 @@ function ProjectPages(handle) {
360
366
  children: viewports.length > 1 ? viewports.map((option) => /* @__PURE__ */ jsx(IconButton, {
361
367
  tooltip: true,
362
368
  icon: option.mobile ? "mobile" : "desktop",
363
- label: `Show ${option.name} (${option.width})`,
369
+ label: t("app.client.projectPages.showViewport", {
370
+ name: option.name,
371
+ width: option.width
372
+ }),
364
373
  active: option.id === viewport.id,
365
374
  mix: on("click", () => {
366
375
  viewport = option;
@@ -378,7 +387,7 @@ function ProjectPages(handle) {
378
387
  variant: "ui",
379
388
  tone: "muted",
380
389
  class: "max-w-md text-center",
381
- children: "Only the pages in your routes have pictures."
390
+ children: t("app.client.projectPages.routePicturesOnly")
382
391
  })
383
392
  });
384
393
  if (compared) return /* @__PURE__ */ jsx("div", {
@@ -386,14 +395,14 @@ function ProjectPages(handle) {
386
395
  children: /* @__PURE__ */ jsx("div", {
387
396
  class: "flex w-full items-start justify-center gap-4 p-4",
388
397
  children: compared.map((branch) => /* @__PURE__ */ jsx("section", {
389
- "aria-label": `${branch} picture`,
398
+ "aria-label": t("app.client.projectPages.branchPicture", { branch: branchLabel(branch) }),
390
399
  class: "flex min-w-0 flex-1 flex-col gap-2",
391
400
  style: { maxWidth: `${viewport.width}px` },
392
401
  children: [/* @__PURE__ */ jsx(Text, {
393
402
  variant: "ui-caption",
394
403
  tone: "muted",
395
404
  class: "truncate",
396
- children: branch
405
+ children: branchLabel(branch)
397
406
  }), /* @__PURE__ */ jsx(PagePicture, {
398
407
  previews: previewsOf(branch),
399
408
  route,
@@ -447,9 +456,9 @@ function ProjectPages(handle) {
447
456
  handle.update();
448
457
  }
449
458
  function unavailable(path) {
450
- if (!comments.loaded) return "Loading comments…";
451
- if (!comments.snapshot.capabilities.create) return "Comments here are read-only";
452
- if (!pageRoute(project.routes, path)) return "Only pages in your routes take comments. Add this page to bud.config.";
459
+ if (!comments.loaded) return t("app.client.projectPages.loadingComments");
460
+ if (!comments.snapshot.capabilities.create) return t("app.client.projectPages.commentsReadOnly");
461
+ if (!pageRoute(project.routes, path)) return t("app.client.projectPages.routeCommentsOnly");
453
462
  return null;
454
463
  }
455
464
  tabId = workspaceTab();
@@ -477,7 +486,7 @@ function ProjectPages(handle) {
477
486
  if (!branches.loaded && !branches.error) return /* @__PURE__ */ jsx("div", {
478
487
  class: "h-full min-h-0",
479
488
  mix: mount,
480
- children: /* @__PURE__ */ jsx(Loader, { label: "Loading branches…" })
489
+ children: /* @__PURE__ */ jsx(Loader, { label: t("app.client.projectPages.loadingBranches") })
481
490
  });
482
491
  let current = activeTab();
483
492
  if (!pictureMode && typeof window !== "undefined" && new URL(origin).origin === window.location.origin) return /* @__PURE__ */ jsx("div", {
@@ -488,7 +497,7 @@ function ProjectPages(handle) {
488
497
  variant: "ui",
489
498
  tone: "muted",
490
499
  class: "max-w-md text-center",
491
- children: "Your site is served from Bud's own address, so Pages can't show it. Serve it on its own port to browse it here."
500
+ children: t("app.client.projectPages.siteOnBudOrigin")
492
501
  })
493
502
  });
494
503
  return /* @__PURE__ */ jsx("div", {
@@ -525,7 +534,7 @@ function ProjectPages(handle) {
525
534
  unavailable: unavailable(current.path),
526
535
  draft: draft ? {
527
536
  path: draft.pick.path,
528
- label: draft.pick.element?.label ?? "A spot on the page",
537
+ label: draft.pick.element?.label ?? t("app.client.projectPages.spotOnPage"),
529
538
  source: draft.pick.element?.source ?? null
530
539
  } : null,
531
540
  busy: comments.busy,
@@ -544,7 +553,7 @@ function ProjectPages(handle) {
544
553
  design: project.designs.pages,
545
554
  sections: pictureMode || captured().length > 1 ? captured().map((branch) => ({
546
555
  id: branch.name,
547
- label: branch.name,
556
+ label: branchLabel(branch.name),
548
557
  caption: branch.caption
549
558
  })) : void 0,
550
559
  section: current.branch ?? home,
@@ -1,4 +1,4 @@
1
- import { Fi as array, Hi as number, Hn as parsePreview, Ii as boolean, Ki as parseSafe, S as writeRows, Ui as object, Un as parsePreviewBranch, Vi as nullable, ai as parseElementAnchor, b as plainRow, c as previewKey, l as projectStore, qi as string, qn as parsePreviewPins, v as projectLive, y as routes, zi as enum_ } from "./comment-data-CiDuvXi2.js";
1
+ import { Bi as boolean, Gi as nullable, Ki as number, Kn as parsePreview, S as writeRows, Ui as enum_, Xi as parseSafe, Zi as string, Zn as parsePreviewPins, b as plainRow, c as previewKey, l as projectStore, li as parseElementAnchor, pn as t, qi as object, qn as parsePreviewBranch, v as projectLive, y as routes, zi as array } from "./comment-data-DsgJWq4n.js";
2
2
  //#region app/client/project-previews.ts
3
3
  /**
4
4
  * A preview's capture in a theme at a viewport. Captures from before viewports existed name
@@ -162,12 +162,12 @@ async function request(url, token, signal, method = "GET") {
162
162
  });
163
163
  if (!response.ok) {
164
164
  let body = await response.json().catch(() => null);
165
- throw new Error(typeof body?.error === "string" ? body.error : `Preview request failed (${response.status}).`);
165
+ throw new Error(typeof body?.error === "string" ? body.error : t("app.client.projectPreviews.requestFailed", { status: response.status }));
166
166
  }
167
167
  return response;
168
168
  }
169
169
  function message(cause) {
170
- return cause instanceof Error ? cause.message : "Bud could not load this preview.";
170
+ return cause instanceof Error ? cause.message : t("app.client.projectPreviews.loadFailed");
171
171
  }
172
172
  /**
173
173
  * Shows server-captured route screenshots for one canvas from the project's local store,
@@ -501,7 +501,7 @@ var BranchListSchema = object({
501
501
  */
502
502
  function parseBranchList(value) {
503
503
  let result = parseSafe(BranchListSchema, value);
504
- if (!result.success) throw new TypeError("The companion returned an invalid branch list.");
504
+ if (!result.success) throw new TypeError(t("app.client.projectBranches.invalidBranchList"));
505
505
  return result.value;
506
506
  }
507
507
  /**
@@ -532,7 +532,7 @@ function createProjectBranches(options) {
532
532
  signal
533
533
  });
534
534
  let body = await response.json().catch(() => null);
535
- if (!response.ok) throw new Error(typeof body?.error === "string" ? body.error : "Bud could not load branches.");
535
+ if (!response.ok) throw new Error(typeof body?.error === "string" ? body.error : t("app.client.projectBranches.loadError"));
536
536
  return parseBranchList(body);
537
537
  }
538
538
  async function load() {
@@ -1,5 +1,5 @@
1
- import { C as ThreadTaskLinks, Dt as taskHref, Nn as jsx, Si as valueByRole, _ as requestComments, ar as parseThreadLinkInput, er as parseThread, gn as on, in as IconButton, kn as Fragment, mi as parseWorkItemRef, oi as parseWorkWorkspace, pn as ref, w as ThreadTaskPicker, y as routes } from "./comment-data-CiDuvXi2.js";
2
- import { n as requestTasks } from "./tasks-request-Dcrpd2_8.js";
1
+ import { C as ThreadTaskLinks, Dt as taskHref, Ei as valueByRole, Ln as jsx, Nn as Fragment, _ as requestComments, _n as ref, bn as on, in as IconButton, ir as parseThread, lr as parseThreadLinkInput, pn as t, ui as parseWorkWorkspace, vi as parseWorkItemRef, w as ThreadTaskPicker, y as routes } from "./comment-data-DsgJWq4n.js";
2
+ import { n as requestTasks } from "./tasks-request-C89fN1dK.js";
3
3
  //#region app/client/project-task-picker.tsx
4
4
  /**
5
5
  * Loads searchable task choices only while the picker is mounted.
@@ -34,7 +34,7 @@ function ProjectTaskPicker(handle) {
34
34
  });
35
35
  let { schema, snapshot } = await requestTasks(routes.tasksApi.workspace.href() + "?" + search, token, signal, parseWorkWorkspace);
36
36
  signal.throwIfAborted();
37
- if (snapshot.connection.state !== "ready") throw new Error(snapshot.connection.message ?? "The task adapter is unavailable.");
37
+ if (snapshot.connection.state !== "ready") throw new Error(snapshot.connection.message ?? t("app.client.projectTaskPicker.adapterUnavailable"));
38
38
  items = snapshot.items.map((item) => {
39
39
  let title = valueByRole(item, schema, "title");
40
40
  return {
@@ -49,7 +49,7 @@ function ProjectTaskPicker(handle) {
49
49
  }
50
50
  truncated = snapshot.truncated;
51
51
  } catch (cause) {
52
- if (!signal.aborted) error = cause instanceof Error ? cause.message : "Could not load tasks.";
52
+ if (!signal.aborted) error = cause instanceof Error ? cause.message : t("app.client.projectTaskPicker.loadFailed");
53
53
  } finally {
54
54
  if (request === controller) {
55
55
  loading = false;
@@ -104,9 +104,9 @@ function ProjectTaskPicker(handle) {
104
104
  * @returns Bounded mutations, each applied against the previous acknowledged revision.
105
105
  */
106
106
  function planThreadLinks(thread, selected) {
107
- if (selected.length > 100 || new Set(selected).size !== selected.length) throw new Error(`Select up to 100 different tasks.`);
107
+ if (selected.length > 100 || new Set(selected).size !== selected.length) throw new Error(t("app.client.threadLinks.selectUpToTasks", { count: 100 }));
108
108
  for (let id of selected) parseWorkItemRef({ id });
109
- if (!thread.anchor && !selected.length) throw new Error("A task discussion needs at least one linked task.");
109
+ if (!thread.anchor && !selected.length) throw new Error(t("app.client.threadLinks.taskDiscussionNeedsLinkedTask"));
110
110
  let current = new Set(thread.itemIds);
111
111
  let desired = new Set(selected);
112
112
  let changes = [];
@@ -127,7 +127,7 @@ function planThreadLinks(thread, selected) {
127
127
  });
128
128
  for (let id of added) current.add(id);
129
129
  }
130
- if (!removed.length && !added.length) throw new Error("Could not prepare task links.");
130
+ if (!removed.length && !added.length) throw new Error(t("app.client.threadLinks.couldNotPrepareTaskLinks"));
131
131
  }
132
132
  return changes;
133
133
  }
@@ -144,7 +144,7 @@ async function saveThreadLinks(thread, selected, mutate, signal) {
144
144
  let current = thread;
145
145
  for (let change of changes) {
146
146
  signal.throwIfAborted();
147
- if (!current.revision) throw new Error("Review the latest discussion before changing links.");
147
+ if (!current.revision) throw new Error(t("app.client.threadLinks.reviewLatestDiscussionBeforeChangingLinks"));
148
148
  let input = parseThreadLinkInput({
149
149
  itemIds: change.itemIds,
150
150
  expectedRevision: current.revision
@@ -154,7 +154,7 @@ async function saveThreadLinks(thread, selected, mutate, signal) {
154
154
  let expected = new Set(current.itemIds);
155
155
  for (let id of change.itemIds) if (change.action === "link") expected.add(id);
156
156
  else expected.delete(id);
157
- if (next.id !== current.id || !next.revision || next.itemIds.length !== expected.size || next.itemIds.some((id) => !expected.has(id)) || JSON.stringify(next.anchor) !== JSON.stringify(current.anchor)) throw new Error("The task link change was not confirmed. Review the latest links before saving again.");
157
+ if (next.id !== current.id || !next.revision || next.itemIds.length !== expected.size || next.itemIds.some((id) => !expected.has(id)) || JSON.stringify(next.anchor) !== JSON.stringify(current.anchor)) throw new Error(t("app.client.threadLinks.taskLinkChangeNotConfirmed"));
158
158
  current = next;
159
159
  }
160
160
  return current;
@@ -177,7 +177,7 @@ function ProjectThreadLinks(handle) {
177
177
  async function review() {
178
178
  if (!editing) return;
179
179
  let next = await requestComments(routes.comments.get.href({ id: editing.id }), handle.props.project.token, handle.signal, parseThread);
180
- if (next.id !== editing.id) throw new Error("The service returned another discussion.");
180
+ if (next.id !== editing.id) throw new Error(t("app.client.projectThreadLinks.anotherDiscussion"));
181
181
  editing = next;
182
182
  needsReview = false;
183
183
  reviewed = true;
@@ -200,7 +200,7 @@ function ProjectThreadLinks(handle) {
200
200
  }
201
201
  return () => /* @__PURE__ */ jsx(Fragment, { children: [handle.props.part === "action" ? handle.props.canLink ? /* @__PURE__ */ jsx(IconButton, {
202
202
  icon: "tasks",
203
- label: "Link tasks",
203
+ label: t("app.client.projectThreadLinks.linkTasks"),
204
204
  size: "small",
205
205
  tooltip: true,
206
206
  disabled: handle.props.busy,
@@ -218,7 +218,7 @@ function ProjectThreadLinks(handle) {
218
218
  onClose: close,
219
219
  onReview: needsReview ? review : void 0,
220
220
  onSave: async (selected) => {
221
- if (!editing || needsReview) throw new Error("Review the latest links before saving.");
221
+ if (!editing || needsReview) throw new Error(t("app.client.projectThreadLinks.reviewLinksBeforeSaving"));
222
222
  try {
223
223
  let initial = editing;
224
224
  editing = await saveThreadLinks(initial, selected, (action, input) => {
@@ -1,4 +1,4 @@
1
- import { _ as requestComments, a as saveThreads, b as plainRow, cn as toast, er as parseThread, g as CommentsRequestError, i as saveThread, l as projectStore, n as unavailableComments, nr as parseThreadFilter, o as threadMatches, rr as parseThreadId, sr as parseThreadSnapshot, x as writeOptimistically, y as routes } from "./comment-data-CiDuvXi2.js";
1
+ import { _ as requestComments, a as saveThreads, b as plainRow, cn as toast, dr as parseThreadSnapshot, g as CommentsRequestError, i as saveThread, ir as parseThread, l as projectStore, n as unavailableComments, o as threadMatches, or as parseThreadFilter, pn as t, sr as parseThreadId, x as writeOptimistically, y as routes } from "./comment-data-DsgJWq4n.js";
2
2
  //#region ../work/src/lib/predict.ts
3
3
  /**
4
4
  * How a new task shows until its adapter confirms it: the requested values, and the creation
@@ -161,7 +161,7 @@ async function loadItemComments(token, itemId, signal, conversationId) {
161
161
  signal.throwIfAborted();
162
162
  return {
163
163
  snapshot: null,
164
- error: cause instanceof Error ? cause.message : "Could not load the discussion."
164
+ error: cause instanceof Error ? cause.message : t("app.client.projectComments.couldNotLoadDiscussion")
165
165
  };
166
166
  }
167
167
  }
@@ -220,7 +220,7 @@ async function loadComments(project, signal, fetcher = fetch) {
220
220
  signal.throwIfAborted();
221
221
  return {
222
222
  snapshot: null,
223
- error: cause instanceof Error ? cause.message : "Could not load comments."
223
+ error: cause instanceof Error ? cause.message : t("app.client.projectComments.couldNotLoadComments")
224
224
  };
225
225
  }
226
226
  }
@@ -307,7 +307,7 @@ function createProjectComments(options) {
307
307
  error = "";
308
308
  }
309
309
  } catch (cause) {
310
- if (!signal.aborted && turn === generation) error = cause instanceof Error ? cause.message : "Could not load comments.";
310
+ if (!signal.aborted && turn === generation) error = cause instanceof Error ? cause.message : t("app.client.projectComments.couldNotLoadComments");
311
311
  } finally {
312
312
  if (turn === generation) {
313
313
  loading = false;
@@ -327,16 +327,16 @@ function createProjectComments(options) {
327
327
  changed();
328
328
  try {
329
329
  let thread = await request(routes.comments.get.href({ id }), signal, parseThread);
330
- if (thread.id !== id) throw new CommentsRequestError("The comments service returned another discussion.");
330
+ if (thread.id !== id) throw new CommentsRequestError(t("app.client.projectComments.serviceReturnedAnotherDiscussion"));
331
331
  if (!signal.aborted && turn === generation) {
332
332
  await merge(thread);
333
- if (!includes(thread)) throw new CommentsRequestError("This discussion is no longer part of this view.");
333
+ if (!includes(thread)) throw new CommentsRequestError(t("app.client.projectComments.discussionNoLongerInView"));
334
334
  if (!detailFailures.size) error = "";
335
335
  }
336
336
  } catch (cause) {
337
337
  if (!signal.aborted && turn === generation) {
338
338
  detailFailures.add(id);
339
- error = cause instanceof Error ? cause.message : "Could not load the discussion.";
339
+ error = cause instanceof Error ? cause.message : t("app.client.projectComments.couldNotLoadDiscussion");
340
340
  }
341
341
  } finally {
342
342
  if (details.get(id) === controller) {
@@ -346,10 +346,10 @@ function createProjectComments(options) {
346
346
  }
347
347
  }
348
348
  function requireCapability(capability) {
349
- if (busy) throw new Error("Wait for the current comment operation to finish.");
350
- if (!loaded) throw new Error("Load comments before saving.");
351
- if (snapshot.connection.state !== "ready") throw new Error(snapshot.connection.message ?? "The comment adapter is unavailable.");
352
- if (!snapshot.capabilities[capability]) throw new Error("The comment adapter does not support this operation.");
349
+ if (busy) throw new Error(t("app.client.projectComments.waitForCurrentOperation"));
350
+ if (!loaded) throw new Error(t("app.client.projectComments.loadCommentsBeforeSaving"));
351
+ if (snapshot.connection.state !== "ready") throw new Error(snapshot.connection.message ?? t("app.client.projectComments.adapterUnavailable"));
352
+ if (!snapshot.capabilities[capability]) throw new Error(t("app.client.projectComments.adapterDoesNotSupportOperation"));
353
353
  }
354
354
  async function operate(url, body, parse, apply, success, threadId, predict) {
355
355
  cancelReads();
@@ -371,13 +371,13 @@ function createProjectComments(options) {
371
371
  return snapshot;
372
372
  } catch (cause) {
373
373
  if (!options.signal.aborted) {
374
- error = cause instanceof Error ? cause.message : "The comment write was not confirmed.";
375
- if (predict) toast.error("Bud undid a comment change", { description: error });
374
+ error = cause instanceof Error ? cause.message : t("app.client.projectComments.commentWriteNotConfirmed");
375
+ if (predict) toast.error(t("app.client.projectComments.budUndidCommentChange"), { description: error });
376
376
  if (cause instanceof CommentsRequestError && cause.code === "conflict") try {
377
377
  await adopt(await request(listHref(filter), options.signal, parseThreadSnapshot));
378
378
  if (threadId) {
379
379
  let detail = await request(routes.comments.get.href({ id: threadId }), options.signal, parseThread);
380
- if (detail.id !== threadId) throw new CommentsRequestError("The comments service returned another discussion.");
380
+ if (detail.id !== threadId) throw new CommentsRequestError(t("app.client.projectComments.serviceReturnedAnotherDiscussion"));
381
381
  await merge(detail);
382
382
  }
383
383
  } catch {}
@@ -422,7 +422,7 @@ function createProjectComments(options) {
422
422
  let pending = `pending-${crypto.randomUUID()}`;
423
423
  if (change.id) {
424
424
  let id = parseThreadId(change.id), thread = snapshot.threads.find((item) => item.id === id);
425
- if (!thread?.revision) throw new Error("Reload this discussion before saving.");
425
+ if (!thread?.revision) throw new Error(t("app.client.projectComments.reloadDiscussionBeforeSaving"));
426
426
  let next;
427
427
  if (change.status !== void 0) {
428
428
  url = routes.comments.status.href({ id });
@@ -467,16 +467,16 @@ function createProjectComments(options) {
467
467
  let saved;
468
468
  return {
469
469
  ...await operate(url, body, parseThread, (thread) => {
470
- if (change.id && thread.id !== change.id) throw new CommentsRequestError("The comment write could not be confirmed. Refresh before trying again.", 409, "uncertain");
470
+ if (change.id && thread.id !== change.id) throw new CommentsRequestError(t("app.client.projectComments.refreshBeforeTryingAgain"), 409, "uncertain");
471
471
  saved = thread;
472
472
  return merge(thread);
473
- }, "Comment saved", change.id, predict),
473
+ }, t("app.client.projectComments.commentSaved"), change.id, predict),
474
474
  thread: saved
475
475
  };
476
476
  },
477
477
  async sync() {
478
478
  requireCapability("sync");
479
- return operate(routes.comments.sync.href(), { direction: "both" }, parseThreadSnapshot, adopt, "Comments synced");
479
+ return operate(routes.comments.sync.href(), { direction: "both" }, parseThreadSnapshot, adopt, t("app.client.projectComments.commentsSynced"));
480
480
  }
481
481
  };
482
482
  }
@@ -1,8 +1,8 @@
1
- import { $t as Button, B as TaskWorkspace, C as ThreadTaskLinks, Dt as taskHref, Et as selectedTask, Jt as CommentThreadDetail, Nn as jsx, Ot as taskRole, S as writeRows, T as ItemDiscussion, Tt as readConversationLocation, Xt as CommentComposer, Yt as CommentThreadItem, b as plainRow, cn as toast, dn as Text, fi as parseWorkDetail, gn as on, h as storedWorkspace, hi as parseWorkMutationResult, kn as Fragment, l as projectStore, nt as CommentThreadHeading, pn as ref, qt as taskRoutes, si as parseSectionData, t as commentsUnavailable, x as writeOptimistically, xt as conversationLabel, y as routes, yi as parseWorkSyncResult } from "./comment-data-CiDuvXi2.js";
2
- import { a as predictWorkAction, i as loadItemComments, n as createProjectComments, o as predictWorkCreate, s as predictWorkUpdate, t as defineScreen } from "./screen-BroBb1Z_.js";
3
- import { n as requestTasks, t as TaskRequestError } from "./tasks-request-Dcrpd2_8.js";
1
+ import { $t as Button, B as TaskWorkspace, C as ThreadTaskLinks, Ci as parseWorkSyncResult, Dt as taskHref, Et as selectedTask, Jt as CommentThreadDetail, Ln as jsx, Nn as Fragment, Ot as taskRole, S as writeRows, T as ItemDiscussion, Tt as readConversationLocation, Xt as CommentComposer, Yt as CommentThreadItem, _n as ref, b as plainRow, bn as on, cn as toast, di as parseSectionData, gi as parseWorkDetail, h as storedWorkspace, hn as Text, l as projectStore, nt as CommentThreadHeading, pn as t, qt as taskRoutes, x as writeOptimistically, xt as conversationLabel, y as routes, yi as parseWorkMutationResult } from "./comment-data-DsgJWq4n.js";
2
+ import { a as predictWorkAction, i as loadItemComments, n as createProjectComments, o as predictWorkCreate, s as predictWorkUpdate, t as defineScreen } from "./screen-QmqmI2Aq.js";
3
+ import { n as requestTasks, t as TaskRequestError } from "./tasks-request-C89fN1dK.js";
4
4
  import { r as projectWork } from "../hosted.js";
5
- import { n as ProjectTaskPicker, t as ProjectThreadLinks } from "./project-thread-links-B1Y2bDUx.js";
5
+ import { n as ProjectTaskPicker, t as ProjectThreadLinks } from "./project-thread-links-BmY8HQXt.js";
6
6
  //#region app/client/task-discussion.tsx
7
7
  /**
8
8
  * Owns a task's filtered discussions and lazy detail requests within its mounted lifetime.
@@ -70,13 +70,13 @@ function TaskDiscussion(handle) {
70
70
  creating = false;
71
71
  handle.update();
72
72
  }),
73
- children: "Reply"
73
+ children: t("app.client.taskDiscussion.reply")
74
74
  }) : null]
75
75
  });
76
76
  }
77
77
  function heading(thread) {
78
78
  return /* @__PURE__ */ jsx(CommentThreadHeading, {
79
- location: thread.anchor ? conversationLabel(thread) : "Discussion",
79
+ location: thread.anchor ? conversationLabel(thread) : t("app.client.taskDiscussion.discussion"),
80
80
  actions: handle.props.renderThreadLinks ? void 0 : /* @__PURE__ */ jsx(ProjectThreadLinks, {
81
81
  part: "action",
82
82
  ...threadLinks(thread)
@@ -110,7 +110,7 @@ function TaskDiscussion(handle) {
110
110
  let count = threads.reduce((sum, thread) => sum + thread.messageCount, 0);
111
111
  return /* @__PURE__ */ jsx("section", {
112
112
  class: "flex min-w-0 flex-col gap-3",
113
- "aria-label": "Task discussion",
113
+ "aria-label": t("app.client.taskDiscussion.taskDiscussion"),
114
114
  mix: mount,
115
115
  children: [
116
116
  /* @__PURE__ */ jsx("div", {
@@ -120,7 +120,7 @@ function TaskDiscussion(handle) {
120
120
  as: "h3",
121
121
  variant: "ui",
122
122
  weight: "medium",
123
- children: "Discussion"
123
+ children: t("app.client.taskDiscussion.discussion")
124
124
  }),
125
125
  count ? /* @__PURE__ */ jsx(Text, {
126
126
  variant: "ui-caption",
@@ -136,7 +136,7 @@ function TaskDiscussion(handle) {
136
136
  creating = true;
137
137
  handle.update();
138
138
  }),
139
- children: "New discussion"
139
+ children: t("app.client.taskDiscussion.newDiscussion")
140
140
  }) : null
141
141
  ]
142
142
  }),
@@ -152,18 +152,18 @@ function TaskDiscussion(handle) {
152
152
  mix: on("click", () => {
153
153
  comments.load().then(() => selected ? comments.loadThread(selected, true) : void 0);
154
154
  }),
155
- children: "Reload comments"
155
+ children: t("app.client.taskDiscussion.reloadComments")
156
156
  })]
157
157
  }) : null,
158
158
  !comments.loaded ? /* @__PURE__ */ jsx(Text, {
159
159
  variant: "ui-caption",
160
160
  tone: "muted",
161
161
  role: "status",
162
- children: "Loading discussion…"
162
+ children: t("app.client.taskDiscussion.loadingDiscussion")
163
163
  }) : !comments.ready ? /* @__PURE__ */ jsx(Text, {
164
164
  variant: "ui-caption",
165
165
  tone: "muted",
166
- children: commentsUnavailable
166
+ children: t("app.client.taskDiscussion.commentsUnavailable")
167
167
  }) : /* @__PURE__ */ jsx(Fragment, { children: [threads.length ? threads.map((thread) => !thread.anchor ? /* @__PURE__ */ jsx(ItemDiscussion, {
168
168
  thread,
169
169
  links: links(thread)
@@ -198,12 +198,12 @@ function TaskDiscussion(handle) {
198
198
  }, thread.id)) : /* @__PURE__ */ jsx(Text, {
199
199
  variant: "ui-caption",
200
200
  tone: "muted",
201
- children: "No discussion yet."
201
+ children: t("app.client.taskDiscussion.noDiscussionYet")
202
202
  }), capabilities.create || capabilities.reply && (explicit || threads.some((thread) => !thread.anchor)) ? /* @__PURE__ */ jsx("div", {
203
203
  class: "flex min-w-0 flex-col gap-2",
204
204
  children: [capabilities.create && (creating || !explicit && !threads.length) ? /* @__PURE__ */ jsx("div", {
205
205
  class: "flex items-start justify-between gap-2",
206
- "aria-label": "New discussion tasks",
206
+ "aria-label": t("app.client.taskDiscussion.newDiscussionTasks"),
207
207
  children: [/* @__PURE__ */ jsx(ThreadTaskLinks, {
208
208
  itemIds: draftItemIds,
209
209
  items: handle.props.items ?? [],
@@ -221,19 +221,19 @@ function TaskDiscussion(handle) {
221
221
  creating = false;
222
222
  handle.update();
223
223
  }),
224
- children: "Cancel"
224
+ children: t("app.client.taskDiscussion.cancel")
225
225
  }) : null]
226
226
  }) : threads.length > 1 ? /* @__PURE__ */ jsx(Text, {
227
227
  variant: "ui-caption",
228
228
  tone: "muted",
229
- children: ["Replying to ", reply?.messages[0]?.author.name ?? "the discussion"]
229
+ children: t("app.client.taskDiscussion.replyingTo", { name: reply?.messages[0]?.author.name ?? t("app.client.taskDiscussion.theDiscussion") })
230
230
  }) : null, /* @__PURE__ */ jsx(CommentComposer, {
231
- label: "Add task comment",
232
- placeholder: "Add context for your team…",
231
+ label: t("app.client.taskDiscussion.addTaskComment"),
232
+ placeholder: t("app.client.taskDiscussion.addContextPlaceholder"),
233
233
  busy: comments.busy || replyPending,
234
234
  class: "w-full",
235
235
  onSubmit: async (body) => {
236
- if (replyPending) throw new Error("Load the selected discussion before replying.");
236
+ if (replyPending) throw new Error(t("app.client.taskDiscussion.loadBeforeReplying"));
237
237
  if (reply && capabilities.reply) {
238
238
  await write({
239
239
  id: reply.id,
@@ -283,7 +283,7 @@ async function loadTask(token, id, schema, signal, conversationId) {
283
283
  schema
284
284
  ]);
285
285
  let detail = parseWorkDetail(value, resolved);
286
- if (detail.item.id !== id) throw new Error("The task service returned a different task. Refresh and try again.");
286
+ if (detail.item.id !== id) throw new Error(t("app.client.projectTasks.differentTaskRefresh"));
287
287
  return {
288
288
  detail,
289
289
  discussion
@@ -310,7 +310,7 @@ async function loadTasks(project, signal, href, conversationId) {
310
310
  if (!stored || cause instanceof TaskRequestError) throw cause;
311
311
  return open(projectWork(project).refresh(signal).then((value) => value.schema));
312
312
  }).catch((cause) => {
313
- taskError = cause instanceof Error ? cause.message : "Could not load this task.";
313
+ taskError = cause instanceof Error ? cause.message : t("app.client.projectTasks.couldNotLoadTask");
314
314
  return null;
315
315
  }) : null;
316
316
  try {
@@ -328,7 +328,7 @@ async function loadTasks(project, signal, href, conversationId) {
328
328
  return {
329
329
  store,
330
330
  stored: false,
331
- error: cause instanceof Error ? cause.message : "Could not load tasks."
331
+ error: cause instanceof Error ? cause.message : t("app.client.projectTasks.couldNotLoadTasks")
332
332
  };
333
333
  }
334
334
  }
@@ -382,7 +382,7 @@ function ProjectTasks(handle) {
382
382
  };
383
383
  }
384
384
  function currentSchema() {
385
- if (!workspace) throw new Error("Refresh tasks before making changes.");
385
+ if (!workspace) throw new Error(t("app.client.projectTasks.refreshBeforeChanges"));
386
386
  return workspace.schema;
387
387
  }
388
388
  async function write(url, body, parse) {
@@ -419,7 +419,7 @@ function ProjectTasks(handle) {
419
419
  ...body,
420
420
  expectedRevision: prior.to
421
421
  } : body, (value) => parseWorkMutationResult(value, currentSchema()));
422
- if (id && result.item.id !== id) throw new Error("The task service returned a different task. Refresh before retrying.");
422
+ if (id && result.item.id !== id) throw new Error(t("app.client.projectTasks.differentTaskRefreshRetry"));
423
423
  await accepted(result.item);
424
424
  settled.resolve(from ? {
425
425
  from,
@@ -431,7 +431,7 @@ function ProjectTasks(handle) {
431
431
  return item;
432
432
  } catch (cause) {
433
433
  settled.resolve(null);
434
- if (!handle.signal.aborted) toast.error("Bud undid a task change", { description: cause instanceof Error ? cause.message : "The write was not confirmed." });
434
+ if (!handle.signal.aborted) toast.error(t("app.client.projectTasks.undidTaskChange"), { description: cause instanceof Error ? cause.message : t("app.client.projectTasks.writeNotConfirmed") });
435
435
  refreshAfterWrite();
436
436
  throw cause;
437
437
  } finally {
@@ -456,7 +456,7 @@ function ProjectTasks(handle) {
456
456
  act: (id, action) => save(routes.tasksApi.mutate.href({ id }), action, predicted(id, (item) => predictWorkAction(currentSchema(), item, action)), id),
457
457
  get: async (id) => {
458
458
  let next = await requestTasks(routes.tasksApi.show.href({ id }), project.token, handle.signal, (value) => parseWorkDetail(value, currentSchema()));
459
- if (next.item.id !== id) throw new Error("The task service returned a different task. Refresh and try again.");
459
+ if (next.item.id !== id) throw new Error(t("app.client.projectTasks.differentTaskRefresh"));
460
460
  return next.item;
461
461
  },
462
462
  section: (id, section, signal = handle.signal) => requestTasks(routes.tasksApi.section.href({
@@ -487,7 +487,7 @@ function ProjectTasks(handle) {
487
487
  try {
488
488
  await work.refresh(AbortSignal.any([handle.signal, controller.signal]));
489
489
  } catch (cause) {
490
- if (!controller.signal.aborted && !handle.signal.aborted) error = cause instanceof Error ? cause.message : "Could not load tasks.";
490
+ if (!controller.signal.aborted && !handle.signal.aborted) error = cause instanceof Error ? cause.message : t("app.client.projectTasks.couldNotLoadTasks");
491
491
  } finally {
492
492
  if (listRequest === controller) {
493
493
  loading = false;
@@ -523,10 +523,10 @@ function ProjectTasks(handle) {
523
523
  }
524
524
  let next = await requestTasks(routes.tasksApi.show.href({ id }), project.token, signal, (value) => parseWorkDetail(value, currentSchema()));
525
525
  if (controller.signal.aborted || id !== selectedId) return;
526
- if (next.item.id !== id) throw new Error("The task service returned a different task. Refresh and try again.");
526
+ if (next.item.id !== id) throw new Error(t("app.client.projectTasks.differentTaskRefresh"));
527
527
  detail = next;
528
528
  } catch (cause) {
529
- if (!controller.signal.aborted && !handle.signal.aborted && id === selectedId) detailError = cause instanceof Error ? cause.message : "Could not load this task.";
529
+ if (!controller.signal.aborted && !handle.signal.aborted && id === selectedId) detailError = cause instanceof Error ? cause.message : t("app.client.projectTasks.couldNotLoadTask");
530
530
  } finally {
531
531
  if (detailRequest === controller) {
532
532
  detailLoading = false;