@bud.sh/app 0.3.0 → 0.4.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 (77) hide show
  1. package/dist/client/assets/bud/design.js +1 -1
  2. package/dist/client/assets/canvas-DIKX_HCJ.js +1 -0
  3. package/dist/client/assets/difference-worker-CwQNxTJL.js +8 -0
  4. package/dist/client/assets/files-BQ9O3Z8-.js +1 -0
  5. package/dist/client/assets/i18n-CL0jBP7Y.js +1 -0
  6. package/dist/client/assets/index-COvyj5zV.js +2 -0
  7. package/dist/client/assets/pages-BGl2Yuza.js +1 -0
  8. package/dist/client/assets/project-branches-DwKjCwZv.js +1 -0
  9. package/dist/client/assets/project-comments-Cdip2Y10.js +1 -0
  10. package/dist/client/assets/project-thread-links-BGIkGQeU.js +1 -0
  11. package/dist/client/assets/{project-threads-DezXiVK6.js → project-threads-DeKXNqeP.js} +104 -104
  12. package/dist/client/assets/{project-work-CB1JHqGW.js → project-work-B7PGQtV9.js} +1 -1
  13. package/dist/client/assets/task-design-DJp5yho1.js +23 -0
  14. package/dist/client/assets/tasks-BHXj727C.js +1 -0
  15. package/dist/client/assets/tasks-request-CsUB5__X.js +1 -0
  16. package/dist/client/assets/workspace-CbO7RsI2.js +2 -0
  17. package/dist/client/index.html +2 -1
  18. package/dist/hosted/assets/{canvas-C69-96GR.js → canvas-gWj_ocKK.js} +69 -41
  19. package/dist/hosted/assets/{comment-data-CiDuvXi2.js → comment-data-C-rNCB1n.js} +7323 -1068
  20. package/dist/hosted/assets/{files-DQL5hzs3.js → files-CAPvbavY.js} +92 -89
  21. package/dist/hosted/assets/{pages-DFs7jB6M.js → pages-DJiOvT-l.js} +31 -22
  22. package/dist/hosted/assets/{project-branches-CffV4IS1.js → project-branches-BgrfdvtL.js} +5 -5
  23. package/dist/hosted/assets/{project-thread-links-B1Y2bDUx.js → project-thread-links-B7uzThL9.js} +12 -12
  24. package/dist/hosted/assets/{screen-BroBb1Z_.js → screen-D4D9nh4V.js} +18 -18
  25. package/dist/hosted/assets/{tasks-mzmNhe2d.js → tasks-C7aGLzBw.js} +29 -29
  26. package/dist/hosted/assets/{tasks-request-Dcrpd2_8.js → tasks-request-CikFdCgP.js} +7 -7
  27. package/dist/hosted/hosted.js +58 -31
  28. package/dist/node/design.js +6355 -139
  29. package/dist/node/index.js +4 -2
  30. package/dist/node-host/assets/{canvas-BZhzizzt.js → canvas-kXzp80a5.js} +70 -42
  31. package/dist/node-host/assets/{comment-data-nUlI0szN.js → comment-data-CeuJbJKJ.js} +7349 -1094
  32. package/dist/node-host/assets/{files-CTzcWGjb.js → files-0YIuvKzb.js} +92 -89
  33. package/dist/node-host/assets/{pages-0aWH7z1j.js → pages-DOF22SyG.js} +31 -22
  34. package/dist/node-host/assets/{project-branches-1zs_0J_y.js → project-branches-BeEwMBIw.js} +5 -5
  35. package/dist/node-host/assets/{project-thread-links-LpUAGcDr.js → project-thread-links-Bs_MtfBC.js} +12 -12
  36. package/dist/node-host/assets/{screen-DQoTOWYR.js → screen-DRTAKkn3.js} +18 -18
  37. package/dist/node-host/assets/{tasks-C0WqnM7Q.js → tasks-BA_hcbj5.js} +30 -30
  38. package/dist/node-host/assets/{tasks-request-Bwky5ORf.js → tasks-request-Doc9y_20.js} +7 -7
  39. package/dist/node-host/node-host.js +58 -31
  40. package/dist/server/assets/{branch-previews-BryeA_kA.js → branch-previews-ClkoJ0lM.js} +4 -2
  41. package/dist/server/assets/{canvas-D6tIkONI.js → canvas-CiN6jNIZ.js} +70 -42
  42. package/dist/server/assets/{comment-data-BuArfgcc.js → comment-data-9SqALQjT.js} +7347 -1092
  43. package/dist/server/assets/{files-DkrEB48U.js → files-DBVqeLdL.js} +92 -89
  44. package/dist/server/assets/{listener-oKd5ETMj.js → listener-DGWKUc2o.js} +53 -31
  45. package/dist/server/assets/{pages-iEdmK_o2.js → pages-DJMLr6j9.js} +31 -22
  46. package/dist/server/assets/{project-branches-B7rtrvMl.js → project-branches-DbKEmiQT.js} +5 -5
  47. package/dist/server/assets/{project-thread-links-ue_ad9IX.js → project-thread-links-C0kV_Pcl.js} +12 -12
  48. package/dist/server/assets/{project-work-6aOyja4v.js → project-work-C0FN3w5Y.js} +2 -2
  49. package/dist/server/assets/{screen-DMUB9a-K.js → screen-BtvzEiIa.js} +18 -18
  50. package/dist/server/assets/{server-DN5uwE4k.js → server-CYvuOSBA.js} +1 -1
  51. package/dist/server/assets/{services-Ber8opFE.js → services-DK4fqrlF.js} +2 -2
  52. package/dist/server/assets/{tasks-C2oa2wSC.js → tasks-DX71x8f9.js} +31 -31
  53. package/dist/server/assets/{tasks-request-Bk5EoY_p.js → tasks-request-C3jk9Jyu.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-Cuauq8xY.d.ts → config-DIC2cf_S.d.ts} +3 -0
  59. package/dist/types/assets/{contract-BsCQbbxD.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 +2 -2
  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-GPPa1XPO.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-CznMN7F6.js +0 -2
@@ -1,9 +1,9 @@
1
- import { Dn as tryPromise, Dt as callback, Ft as fail$1, Gt as map, Kt as mapError, Lt as flatMap, Mn as void_, Nt as die, On as try_, Pt as ensuring, Sn as sync, St as andThen, Ut as gen, Zt as promise, g as resolveScreenRoutes, gn as scoped, gr as TaggedError, h as defaultScreens, qt as match, s as Previews, t as BranchPreviews, u as Git, v as themeStyle, vt as acquireRelease, y as LOGO_TYPES } from "./branch-previews-BryeA_kA.js";
2
- import { C as selected$1, I as WorkError, L as ScreenModules, O as AdapterRegistry, S as ready, T as LiveChannel, V as taskDesignModuleId, a as FileMutations, b as read, c as FileReview, f as Files, g as StoreActivity, h as ReviewActionError, m as FilesInputError, r as FileSaves, t as FileReviewActions, u as FileSearch, w as validate, x as readWorkspace, y as inspect } from "./server-DN5uwE4k.js";
1
+ import { Dn as tryPromise, Dt as callback, Ft as fail$1, Gt as map, Kt as mapError, Lt as flatMap, Mn as void_, Nt as die, On as try_, Pt as ensuring, Sn as sync, St as andThen, Ut as gen, Zt as promise, g as resolveScreenRoutes, gn as scoped, gr as TaggedError, h as defaultScreens, qt as match, s as Previews, t as BranchPreviews, u as Git, v as themeStyle, vt as acquireRelease, y as LOGO_TYPES } from "./branch-previews-ClkoJ0lM.js";
2
+ import { C as selected$1, I as WorkError, L as ScreenModules, O as AdapterRegistry, S as ready, T as LiveChannel, V as taskDesignModuleId, a as FileMutations, b as read, c as FileReview, f as Files, g as StoreActivity, h as ReviewActionError, m as FilesInputError, r as FileSaves, t as FileReviewActions, u as FileSearch, w as validate, x as readWorkspace, y as inspect } from "./server-CYvuOSBA.js";
3
3
  import { A as parseThreadReplyInput, D as parseThreadId, Dt as parseFileSearchQuery, E as parseThreadFilter, Ht as parseFilePath, N as parseThreadStatusInput, O as parseThreadInput, Rt as parseFileIndexQuery, St as parseReviewSourceQuery, X as parseReviewActionIntent, Xt as parseElementAnchor, Y as parseReviewActionApply, Yt as MAX_CANVAS_Y, _ as taskRoutes, _t as parseReviewHistoryQuery, an as parseWorkDetail, at as parseMutationIntent, cn as parseWorkMutationResult, en as parseSectionData, fn as parseWorkSync, g as pageRoutes, h as fileRoutes, ht as parseReviewDiffQuery, i as routes, in as parseWorkCreate, it as parseMutationApply, j as parseThreadSnapshot, k as parseThreadLinkInput, mn as parseWorkUpdate, pn as parseWorkSyncResult, pt as parseReviewComparison, rt as maxFileSaveBytes, sn as parseWorkItemRef, tn as parseWorkActionInput, tt as parseFileSaveRequest, un as parseWorkQuery, vn as parseMessageAuthor, w as parseThread } from "./events-6z5v7Uca.js";
4
- import { $ as openShellTab, B as parseTaskDesign, Bt as Button, Cn as Text, J as FieldCardContent, Q as initialShellTabs, Rt as Loader, Sn as writePreference, X as activeTab, Y as FieldCardHeader, Z as closeShellTab, _ as requestComments, _n as LiveStatus, _t as taskHref, at as tabsCookieValue, et as parseShellTabs, gt as selectedTask, ht as readConversationLocation, it as tabsCookieName, lt as conversationDestination, mn as ShellTabBar, n as unavailableComments, nt as stepShellTab, ot as visitShellTab, pt as legacyConversationHref, q as FieldCard, r as followThreads, rt as tabHref, t as commentsUnavailable, tt as readTabsCookie, v as projectLive, xn as readPreference, xt as legacyFileHref, yn as AppShell } from "./comment-data-BuArfgcc.js";
4
+ import { $ as openShellTab, B as parseTaskDesign, Bt as Button, Cn as hasMessage, Dn as useLocaleSource, En as t, J as FieldCardContent, On as Text, Q as initialShellTabs, Rt as Loader, Sn as writePreference, Tn as locale, X as activeTab, Y as FieldCardHeader, Z as closeShellTab, _ as requestComments, _n as LiveStatus, _t as taskHref, at as tabsCookieValue, et as parseShellTabs, gt as selectedTask, ht as readConversationLocation, it as tabsCookieName, lt as conversationDestination, mn as ShellTabBar, n as unavailableComments, nt as stepShellTab, ot as visitShellTab, pt as legacyConversationHref, q as FieldCard, r as followThreads, rt as tabHref, t as commentsUnavailable, tt as readTabsCookie, v as projectLive, wn as isLocale, xn as readPreference, xt as legacyFileHref, yn as AppShell } from "./comment-data-9SqALQjT.js";
5
5
  import { d as parsePreviewHash, f as parsePreviewId, l as parsePreviewBranch, m as parsePreviewViewport } from "./parsers-CsiSkwTT.js";
6
- import { t as projectWork } from "./project-work-6aOyja4v.js";
6
+ import { t as projectWork } from "./project-work-C0FN3w5Y.js";
7
7
  import { n as showWorkspaceTab } from "./workspace-tab-DAUjkJzZ.js";
8
8
  import { createHash } from "node:crypto";
9
9
  import { extname, isAbsolute, relative, resolve } from "node:path";
@@ -17,6 +17,7 @@ import { renderToString } from "remix/component/server";
17
17
  import { createRedirectResponse } from "remix/response/redirect";
18
18
  import { readFile, realpath, stat } from "node:fs/promises";
19
19
  import * as form from "remix/data-schema/form-data";
20
+ import { AsyncLocalStorage } from "node:async_hooks";
20
21
  import { createServer } from "node:http";
21
22
  import { createRequestListener } from "remix/node-fetch-server";
22
23
  //#region app/screen-routes.ts
@@ -85,10 +86,10 @@ function ProjectLiveStatus(handle) {
85
86
  //#endregion
86
87
  //#region app/client/screens.ts
87
88
  var builtIns = {
88
- "bud:pages": () => import("./pages-iEdmK_o2.js"),
89
- "bud:canvas": () => import("./canvas-D6tIkONI.js"),
90
- "bud:tasks": () => import("./tasks-C2oa2wSC.js"),
91
- "bud:files": () => import("./files-DkrEB48U.js")
89
+ "bud:pages": () => import("./pages-DJMLr6j9.js"),
90
+ "bud:canvas": () => import("./canvas-CiN6jNIZ.js"),
91
+ "bud:tasks": () => import("./tasks-DX71x8f9.js"),
92
+ "bud:files": () => import("./files-DBVqeLdL.js")
92
93
  };
93
94
  /**
94
95
  * Imports a screen module: a built-in by its key, any other by the URL the server gave it.
@@ -174,7 +175,7 @@ function ScreenHost(handle) {
174
175
  } catch (cause) {
175
176
  if (handle.signal.aborted) return;
176
177
  console.error(cause);
177
- error = cause instanceof Error && cause.message ? cause.message : `Could not open ${label}.`;
178
+ error = cause instanceof Error && cause.message ? cause.message : t("app.client.screenHost.couldNotOpen", { label });
178
179
  }
179
180
  handle.update();
180
181
  }
@@ -186,7 +187,7 @@ function ScreenHost(handle) {
186
187
  let context = opened?.context;
187
188
  let routeError = "";
188
189
  if (opened && context) try {
189
- if (location.routes && !matchScreenRoutes(location.routes, location.href)) throw new Error(`No screen route matches ${location.href}.`);
190
+ if (location.routes && !matchScreenRoutes(location.routes, location.href)) throw new Error(t("app.client.screenHost.noRouteMatches", { href: location.href }));
190
191
  context = matchScreenContext(opened.screen.routes, {
191
192
  ...context,
192
193
  active: handle.props.active,
@@ -194,7 +195,7 @@ function ScreenHost(handle) {
194
195
  url: new URL(location.href, context.url.origin)
195
196
  });
196
197
  } catch (cause) {
197
- routeError = cause instanceof Error ? cause.message : "This screen route is invalid.";
198
+ routeError = cause instanceof Error ? cause.message : t("app.client.screenHost.invalidRoute");
198
199
  }
199
200
  return /* @__PURE__ */ jsx("div", {
200
201
  class: "h-full min-h-0",
@@ -211,7 +212,7 @@ function ScreenHost(handle) {
211
212
  class: "max-w-2xl",
212
213
  role: "alert",
213
214
  children: [/* @__PURE__ */ jsx(FieldCardHeader, {
214
- title: `${label} didn't open`,
215
+ title: t("app.client.screenHost.didntOpen", { label }),
215
216
  subtitle: error
216
217
  }), /* @__PURE__ */ jsxs(FieldCardContent, {
217
218
  class: "flex-col items-start gap-3",
@@ -219,17 +220,17 @@ function ScreenHost(handle) {
219
220
  as: "p",
220
221
  variant: "ui",
221
222
  tone: "muted",
222
- children: "Fix the screen and retry. Details are in the browser console."
223
+ children: t("app.client.screenHost.fixAndRetry")
223
224
  }), /* @__PURE__ */ jsx(Button, {
224
225
  mix: on("click", () => {
225
226
  if ("error" in module || handle.props.taskDesign && "error" in handle.props.taskDesign) window.location.reload();
226
227
  else open();
227
228
  }),
228
- children: "Retry"
229
+ children: t("app.client.screenHost.retry")
229
230
  })]
230
231
  })]
231
232
  })
232
- }) : /* @__PURE__ */ jsx(Loader, { label: `Loading ${label}…` })
233
+ }) : /* @__PURE__ */ jsx(Loader, { label: t("app.client.screenHost.loading", { label }) })
233
234
  });
234
235
  };
235
236
  }
@@ -248,11 +249,11 @@ async function resolveConversation(id, targets, token, signal, fetcher = fetch)
248
249
  id = parseThreadId(id);
249
250
  let thread = await requestComments(routes.comments.get.href({ id }), token, signal, parseThread, fetcher);
250
251
  signal.throwIfAborted();
251
- if (thread.id !== id) throw new Error("The comments service returned another discussion.");
252
+ if (thread.id !== id) throw new Error(t("app.client.conversationRoute.anotherDiscussion"));
252
253
  let destination = conversationDestination(thread);
253
254
  let target = targets.find((target) => target.module === destination.module);
254
- if (!target) throw new Error("Enable the screen that owns this discussion to open it.");
255
- if (target.routes && !matchScreenRoutes(target.routes, destination.href)) throw new Error("The discussion location is not available in this screen.");
255
+ if (!target) throw new Error(t("app.client.conversationRoute.enableOwningScreen"));
256
+ if (target.routes && !matchScreenRoutes(target.routes, destination.href)) throw new Error(t("app.client.conversationRoute.locationUnavailable"));
256
257
  return {
257
258
  active: target.id,
258
259
  module: { url: target.module },
@@ -298,14 +299,14 @@ function Workspace(handle) {
298
299
  changed();
299
300
  try {
300
301
  let project = handle.props.project;
301
- if (!project) throw new Error("Connect a project to open this discussion.");
302
+ if (!project) throw new Error(t("app.client.workspace.connectProjectToOpenDiscussion"));
302
303
  let next = await resolveConversation(id, handle.props.conversationTargets ?? [], project.token, signal);
303
304
  if (!signal.aborted && conversation === id) {
304
305
  presented = next;
305
306
  hrefs.set(next.active, window.location.pathname);
306
307
  }
307
308
  } catch (cause) {
308
- if (!signal.aborted) error = cause instanceof Error ? cause.message : "Could not open this discussion.";
309
+ if (!signal.aborted) error = cause instanceof Error ? cause.message : t("app.client.workspace.couldNotOpenDiscussion");
309
310
  } finally {
310
311
  if (request === controller && !signal.aborted) {
311
312
  loading = false;
@@ -348,7 +349,7 @@ function Workspace(handle) {
348
349
  let { pathname } = new URL(href, "http://bud.local");
349
350
  if (readConversationLocation(href)) return {
350
351
  href,
351
- title: "Discussion",
352
+ title: t("app.client.workspace.discussion"),
352
353
  icon: "message"
353
354
  };
354
355
  let match = handle.props.screens.map((item) => ({
@@ -510,19 +511,19 @@ function Workspace(handle) {
510
511
  children: project ? /* @__PURE__ */ jsxs("div", {
511
512
  class: "h-full min-h-0",
512
513
  mix: mount,
513
- children: [loading ? /* @__PURE__ */ jsx(Loader, { label: "Opening discussion…" }) : error ? /* @__PURE__ */ jsx("div", {
514
+ children: [loading ? /* @__PURE__ */ jsx(Loader, { label: t("app.client.workspace.openingDiscussion") }) : error ? /* @__PURE__ */ jsx("div", {
514
515
  class: "p-3 md:p-4",
515
516
  children: /* @__PURE__ */ jsxs(FieldCard, {
516
517
  class: "max-w-2xl",
517
518
  role: "alert",
518
519
  children: [/* @__PURE__ */ jsx(FieldCardHeader, {
519
- title: "Discussion didn't open",
520
+ title: t("app.client.workspace.discussionDidntOpen"),
520
521
  subtitle: error
521
522
  }), /* @__PURE__ */ jsx(FieldCardContent, { children: /* @__PURE__ */ jsx(Button, {
522
523
  mix: on("click", () => {
523
524
  if (conversation) resolve(conversation);
524
525
  }),
525
- children: "Retry"
526
+ children: t("app.client.workspace.retry")
526
527
  }) })]
527
528
  })
528
529
  }) : null, [...opened].map(([id, { module, taskDesign, location }]) => /* @__PURE__ */ jsx("div", {
@@ -540,12 +541,12 @@ function Workspace(handle) {
540
541
  }) : /* @__PURE__ */ jsxs(FieldCard, {
541
542
  class: "max-w-2xl",
542
543
  children: [/* @__PURE__ */ jsx(FieldCardHeader, {
543
- title: "Your project, side by side.",
544
- subtitle: "Open Bud beside a local project to review its pages, files and tasks."
544
+ title: t("app.client.workspace.yourProjectSideBySide"),
545
+ subtitle: t("app.client.workspace.openBudBesideLocalProject")
545
546
  }), /* @__PURE__ */ jsx(FieldCardContent, { children: /* @__PURE__ */ jsx(Text, {
546
547
  as: "p",
547
548
  variant: "ui",
548
- children: "Start your project with the Bud Vite plugin, or run Bud from its directory. Its screens appear here when the companion connects."
549
+ children: t("app.client.workspace.startProjectWithBudVitePlugin")
549
550
  }) })]
550
551
  })
551
552
  });
@@ -612,6 +613,11 @@ var builtInRoutes = {
612
613
  "bud:tasks": taskRoutes,
613
614
  "bud:files": fileRoutes
614
615
  };
616
+ function screenName(screen) {
617
+ let builtIn = defaultScreens.find((candidate) => candidate.module === screen.module);
618
+ let key = `screens.${builtIn?.id}`;
619
+ return builtIn && screen.label === builtIn.label && hasMessage(key) ? t(key) : screen.label;
620
+ }
615
621
  /**
616
622
  * Connects the route contract to application responses.
617
623
  * @param template - The development or production HTML shell.
@@ -652,7 +658,10 @@ function createAppController(template, project, runtime) {
652
658
  let module = await prepare(screen, request.signal);
653
659
  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;
654
660
  let content = await renderToString(/* @__PURE__ */ jsx(WorkspacePage, {
655
- screens: items,
661
+ screens: items.map((item, index) => ({
662
+ ...item,
663
+ label: screenName(screens[index])
664
+ })),
656
665
  active: screen.id,
657
666
  module,
658
667
  taskDesign,
@@ -679,10 +688,10 @@ function createAppController(template, project, runtime) {
679
688
  ...patterns ? { routes: patterns } : {}
680
689
  }
681
690
  }));
682
- let title = (conversation ? "Discussion" : screen.label).replace(/[&<>]/g, (character) => `&#${character.charCodeAt(0)};`);
691
+ let title = (conversation ? t("app.controller.discussion") : screenName(screen)).replace(/[&<>]/g, (character) => `&#${character.charCodeAt(0)};`);
683
692
  let logo = project?.config.logo;
684
693
  let shell = logo ? withLogoIcons(template, logo, (theme) => routes.logo.href({ theme })) : template;
685
- return new Response(shell.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" } });
694
+ 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" } });
686
695
  }
687
696
  return createController(routes, { actions: {
688
697
  conversation({ params, request }) {
@@ -1885,6 +1894,19 @@ function createBranchesController(project, runtime) {
1885
1894
  } });
1886
1895
  }
1887
1896
  //#endregion
1897
+ //#region app/server/locale.ts
1898
+ var requestLocale = new AsyncLocalStorage();
1899
+ useLocaleSource(() => requestLocale.getStore());
1900
+ /**
1901
+ * Runs a request's handling in its language, so what it renders speaks it.
1902
+ * @param value - The request's locale; English when it isn't one of Bud's.
1903
+ * @param handle - The request's handling.
1904
+ * @returns What the handling returns.
1905
+ */
1906
+ function inLocale(value, handle) {
1907
+ return requestLocale.run(isLocale(value) ? value : "en", handle);
1908
+ }
1909
+ //#endregion
1888
1910
  //#region app/router.ts
1889
1911
  /**
1890
1912
  * Creates the app's HTTP handler for development, production, and tests.
@@ -1895,7 +1917,7 @@ function createBranchesController(project, runtime) {
1895
1917
  * @returns A Remix router with the application's routes and middleware.
1896
1918
  */
1897
1919
  function createApp(template, publicDirectory, project, runtime) {
1898
- let middleware = [];
1920
+ let middleware = [(_context, next) => inLocale(project?.config.locale, next)];
1899
1921
  if (project) middleware.push(async ({ request }, next) => {
1900
1922
  if (!isLoopbackHost(new URL(request.url).hostname)) return new Response("Bud is available on loopback only.", { status: 403 });
1901
1923
  let response = await next();
@@ -1,8 +1,8 @@
1
1
  import { Yt as MAX_CANVAS_Y, g as pageRoutes } from "./events-6z5v7Uca.js";
2
- import { Cn as Text, Rt as Loader, Sn as writePreference, gn as IconButton, hn as Select, l as projectStore, st as PagesWorkspace, xn as readPreference, zt as Spinner } from "./comment-data-BuArfgcc.js";
3
- import { n as createProjectComments, t as defineScreen } from "./screen-DMUB9a-K.js";
2
+ import { En as t, On as Text, Rt as Loader, Sn as writePreference, gn as IconButton, hn as Select, l as projectStore, st as PagesWorkspace, xn as readPreference, zt as Spinner } from "./comment-data-9SqALQjT.js";
3
+ import { n as createProjectComments, t as defineScreen } from "./screen-BtvzEiIa.js";
4
4
  import { r as workspaceTab, t as followWorkspaceTab } from "./workspace-tab-DAUjkJzZ.js";
5
- import { n as createProjectPreviews, r as locatePreview, t as createProjectBranches } from "./project-branches-B7rtrvMl.js";
5
+ import { n as createProjectPreviews, r as locatePreview, t as createProjectBranches } from "./project-branches-DbKEmiQT.js";
6
6
  import { array, number, object, optional, parseSafe, string } from "remix/data-schema";
7
7
  import { on, ref } from "remix/component";
8
8
  import { Fragment, jsx, jsxs } from "remix/component/jsx-runtime";
@@ -44,7 +44,10 @@ function PagePicture(handle) {
44
44
  return /* @__PURE__ */ jsx("div", {
45
45
  class: inline ? "relative w-full" : "absolute inset-0 overflow-auto bg-board",
46
46
  role: "img",
47
- "aria-label": `${route.title} picture at ${size.width} pixels`,
47
+ "aria-label": t("app.client.pagePicture.pictureAt", {
48
+ title: route.title,
49
+ width: size.width
50
+ }),
48
51
  children: current ? /* @__PURE__ */ jsx("div", {
49
52
  class: `relative mx-auto ${picking ? "cursor-crosshair" : ""}`,
50
53
  style: { width: `min(100%, ${current.capture.width}px)` },
@@ -80,7 +83,7 @@ function PagePicture(handle) {
80
83
  variant: "ui",
81
84
  tone: "muted",
82
85
  class: "max-w-md text-center",
83
- children: "This page has no picture at this viewport yet. It appears once its branch is captured."
86
+ children: t("app.client.pagePicture.noPictureYet")
84
87
  }) : /* @__PURE__ */ jsx(Spinner, {
85
88
  size: 18,
86
89
  class: "text-muted"
@@ -192,9 +195,12 @@ function ProjectPages(handle) {
192
195
  }] : []);
193
196
  return pictureMode ? pictured : [{
194
197
  name: workingTree,
195
- caption: "live"
198
+ caption: t("app.client.projectPages.live")
196
199
  }, ...pictured];
197
200
  }
201
+ function branchLabel(name) {
202
+ return name === workingTree ? t("app.client.projectPages.workingTree") : name;
203
+ }
198
204
  let comparing = null;
199
205
  let pickedFrom;
200
206
  let viewport = project.previews.viewports[0];
@@ -329,26 +335,26 @@ function ProjectPages(handle) {
329
335
  if (!pictureMode && options.length < 2 && !project.routes.some((route) => route.flow)) return void 0;
330
336
  let compared = comparing;
331
337
  return {
332
- source: compared ? `Comparing ${compared.join(", ")}` : `Pictures of ${activeTab().branch ?? home}`,
338
+ source: compared ? t("app.client.projectPages.comparing", { branches: compared.join(", ") }) : t("app.client.projectPages.picturesOf", { branch: branchLabel(activeTab().branch ?? home) }),
333
339
  shows: (tab) => pictureMode || Boolean(tab.branch) || Boolean(compared) || Boolean(pageRoute(project.routes, tab.path)?.flow),
334
340
  actions: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(IconButton, {
335
341
  tooltip: true,
336
342
  icon: "compare",
337
- label: compared ? "Stop comparing" : "Compare branches side by side",
343
+ label: compared ? t("app.client.projectPages.stopComparing") : t("app.client.projectPages.compareBranches"),
338
344
  active: Boolean(compared),
339
345
  disabled: !compared && options.length < 2,
340
346
  mix: on("click", toggleComparing)
341
347
  }), compared ? /* @__PURE__ */ jsx("div", {
342
348
  role: "group",
343
- "aria-label": "Branches compared",
349
+ "aria-label": t("app.client.projectPages.branchesCompared"),
344
350
  class: "flex min-w-0 items-center gap-1.5",
345
351
  children: compared.map((name, index) => /* @__PURE__ */ jsx(Select, {
346
- label: `Compared branch ${index + 1}`,
352
+ label: t("app.client.projectPages.comparedBranch", { number: index + 1 }),
347
353
  value: name,
348
354
  class: "max-w-52",
349
355
  options: options.map((option) => ({
350
356
  value: option.name,
351
- label: option.name,
357
+ label: branchLabel(option.name),
352
358
  description: option.caption
353
359
  })),
354
360
  onChange: (value) => {
@@ -364,7 +370,10 @@ function ProjectPages(handle) {
364
370
  children: viewports.length > 1 ? viewports.map((option) => /* @__PURE__ */ jsx(IconButton, {
365
371
  tooltip: true,
366
372
  icon: option.mobile ? "mobile" : "desktop",
367
- label: `Show ${option.name} (${option.width})`,
373
+ label: t("app.client.projectPages.showViewport", {
374
+ name: option.name,
375
+ width: option.width
376
+ }),
368
377
  active: option.id === viewport.id,
369
378
  mix: on("click", () => {
370
379
  viewport = option;
@@ -382,7 +391,7 @@ function ProjectPages(handle) {
382
391
  variant: "ui",
383
392
  tone: "muted",
384
393
  class: "max-w-md text-center",
385
- children: "Only the pages in your routes have pictures."
394
+ children: t("app.client.projectPages.routePicturesOnly")
386
395
  })
387
396
  });
388
397
  if (compared) return /* @__PURE__ */ jsx("div", {
@@ -390,14 +399,14 @@ function ProjectPages(handle) {
390
399
  children: /* @__PURE__ */ jsx("div", {
391
400
  class: "flex w-full items-start justify-center gap-4 p-4",
392
401
  children: compared.map((branch) => /* @__PURE__ */ jsxs("section", {
393
- "aria-label": `${branch} picture`,
402
+ "aria-label": t("app.client.projectPages.branchPicture", { branch: branchLabel(branch) }),
394
403
  class: "flex min-w-0 flex-1 flex-col gap-2",
395
404
  style: { maxWidth: `${viewport.width}px` },
396
405
  children: [/* @__PURE__ */ jsx(Text, {
397
406
  variant: "ui-caption",
398
407
  tone: "muted",
399
408
  class: "truncate",
400
- children: branch
409
+ children: branchLabel(branch)
401
410
  }), /* @__PURE__ */ jsx(PagePicture, {
402
411
  previews: previewsOf(branch),
403
412
  route,
@@ -451,9 +460,9 @@ function ProjectPages(handle) {
451
460
  handle.update();
452
461
  }
453
462
  function unavailable(path) {
454
- if (!comments.loaded) return "Loading comments…";
455
- if (!comments.snapshot.capabilities.create) return "Comments here are read-only";
456
- if (!pageRoute(project.routes, path)) return "Only pages in your routes take comments. Add this page to bud.config.";
463
+ if (!comments.loaded) return t("app.client.projectPages.loadingComments");
464
+ if (!comments.snapshot.capabilities.create) return t("app.client.projectPages.commentsReadOnly");
465
+ if (!pageRoute(project.routes, path)) return t("app.client.projectPages.routeCommentsOnly");
457
466
  return null;
458
467
  }
459
468
  tabId = workspaceTab();
@@ -481,7 +490,7 @@ function ProjectPages(handle) {
481
490
  if (!branches.loaded && !branches.error) return /* @__PURE__ */ jsx("div", {
482
491
  class: "h-full min-h-0",
483
492
  mix: mount,
484
- children: /* @__PURE__ */ jsx(Loader, { label: "Loading branches…" })
493
+ children: /* @__PURE__ */ jsx(Loader, { label: t("app.client.projectPages.loadingBranches") })
485
494
  });
486
495
  let current = activeTab();
487
496
  if (!pictureMode && typeof window !== "undefined" && new URL(origin).origin === window.location.origin) return /* @__PURE__ */ jsx("div", {
@@ -492,7 +501,7 @@ function ProjectPages(handle) {
492
501
  variant: "ui",
493
502
  tone: "muted",
494
503
  class: "max-w-md text-center",
495
- 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."
504
+ children: t("app.client.projectPages.siteOnBudOrigin")
496
505
  })
497
506
  });
498
507
  return /* @__PURE__ */ jsx("div", {
@@ -529,7 +538,7 @@ function ProjectPages(handle) {
529
538
  unavailable: unavailable(current.path),
530
539
  draft: draft ? {
531
540
  path: draft.pick.path,
532
- label: draft.pick.element?.label ?? "A spot on the page",
541
+ label: draft.pick.element?.label ?? t("app.client.projectPages.spotOnPage"),
533
542
  source: draft.pick.element?.source ?? null
534
543
  } : null,
535
544
  busy: comments.busy,
@@ -548,7 +557,7 @@ function ProjectPages(handle) {
548
557
  design: project.designs.pages,
549
558
  sections: pictureMode || captured().length > 1 ? captured().map((branch) => ({
550
559
  id: branch.name,
551
- label: branch.name,
560
+ label: branchLabel(branch.name),
552
561
  caption: branch.caption
553
562
  })) : void 0,
554
563
  section: current.branch ?? home,
@@ -1,5 +1,5 @@
1
1
  import { Xt as parseElementAnchor, i as routes } from "./events-6z5v7Uca.js";
2
- import { c as previewKey, l as projectStore, v as projectLive, x as writeRows, y as plainRow } from "./comment-data-BuArfgcc.js";
2
+ import { En as t, c as previewKey, l as projectStore, v as projectLive, x as writeRows, y as plainRow } from "./comment-data-9SqALQjT.js";
3
3
  import { c as parsePreview, l as parsePreviewBranch, p as parsePreviewPins } from "./parsers-CsiSkwTT.js";
4
4
  import { array, boolean, enum_, nullable, number, object, parseSafe, string } from "remix/data-schema";
5
5
  //#region app/client/project-previews.ts
@@ -165,12 +165,12 @@ async function request(url, token, signal, method = "GET") {
165
165
  });
166
166
  if (!response.ok) {
167
167
  let body = await response.json().catch(() => null);
168
- throw new Error(typeof body?.error === "string" ? body.error : `Preview request failed (${response.status}).`);
168
+ throw new Error(typeof body?.error === "string" ? body.error : t("app.client.projectPreviews.requestFailed", { status: response.status }));
169
169
  }
170
170
  return response;
171
171
  }
172
172
  function message(cause) {
173
- return cause instanceof Error ? cause.message : "Bud could not load this preview.";
173
+ return cause instanceof Error ? cause.message : t("app.client.projectPreviews.loadFailed");
174
174
  }
175
175
  /**
176
176
  * Shows server-captured route screenshots for one canvas from the project's local store,
@@ -504,7 +504,7 @@ var BranchListSchema = object({
504
504
  */
505
505
  function parseBranchList(value) {
506
506
  let result = parseSafe(BranchListSchema, value);
507
- if (!result.success) throw new TypeError("The companion returned an invalid branch list.");
507
+ if (!result.success) throw new TypeError(t("app.client.projectBranches.invalidBranchList"));
508
508
  return result.value;
509
509
  }
510
510
  /**
@@ -535,7 +535,7 @@ function createProjectBranches(options) {
535
535
  signal
536
536
  });
537
537
  let body = await response.json().catch(() => null);
538
- if (!response.ok) throw new Error(typeof body?.error === "string" ? body.error : "Bud could not load branches.");
538
+ if (!response.ok) throw new Error(typeof body?.error === "string" ? body.error : t("app.client.projectBranches.loadError"));
539
539
  return parseBranchList(body);
540
540
  }
541
541
  async function load() {
@@ -1,6 +1,6 @@
1
1
  import { Zt as parseWorkWorkspace, gn as valueByRole, i as routes, k as parseThreadLinkInput, sn as parseWorkItemRef, w as parseThread } from "./events-6z5v7Uca.js";
2
- import { C as ThreadTaskPicker, S as ThreadTaskLinks, _ as requestComments, _t as taskHref, gn as IconButton } from "./comment-data-BuArfgcc.js";
3
- import { n as requestTasks } from "./tasks-request-Bk5EoY_p.js";
2
+ import { C as ThreadTaskPicker, En as t, S as ThreadTaskLinks, _ as requestComments, _t as taskHref, gn as IconButton } from "./comment-data-9SqALQjT.js";
3
+ import { n as requestTasks } from "./tasks-request-C3jk9Jyu.js";
4
4
  import { on, ref } from "remix/component";
5
5
  import { Fragment, jsx, jsxs } from "remix/component/jsx-runtime";
6
6
  //#region app/client/project-task-picker.tsx
@@ -37,7 +37,7 @@ function ProjectTaskPicker(handle) {
37
37
  });
38
38
  let { schema, snapshot } = await requestTasks(routes.tasksApi.workspace.href() + "?" + search, token, signal, parseWorkWorkspace);
39
39
  signal.throwIfAborted();
40
- if (snapshot.connection.state !== "ready") throw new Error(snapshot.connection.message ?? "The task adapter is unavailable.");
40
+ if (snapshot.connection.state !== "ready") throw new Error(snapshot.connection.message ?? t("app.client.projectTaskPicker.adapterUnavailable"));
41
41
  items = snapshot.items.map((item) => {
42
42
  let title = valueByRole(item, schema, "title");
43
43
  return {
@@ -52,7 +52,7 @@ function ProjectTaskPicker(handle) {
52
52
  }
53
53
  truncated = snapshot.truncated;
54
54
  } catch (cause) {
55
- if (!signal.aborted) error = cause instanceof Error ? cause.message : "Could not load tasks.";
55
+ if (!signal.aborted) error = cause instanceof Error ? cause.message : t("app.client.projectTaskPicker.loadFailed");
56
56
  } finally {
57
57
  if (request === controller) {
58
58
  loading = false;
@@ -107,9 +107,9 @@ function ProjectTaskPicker(handle) {
107
107
  * @returns Bounded mutations, each applied against the previous acknowledged revision.
108
108
  */
109
109
  function planThreadLinks(thread, selected) {
110
- if (selected.length > 100 || new Set(selected).size !== selected.length) throw new Error(`Select up to 100 different tasks.`);
110
+ if (selected.length > 100 || new Set(selected).size !== selected.length) throw new Error(t("app.client.threadLinks.selectUpToTasks", { count: 100 }));
111
111
  for (let id of selected) parseWorkItemRef({ id });
112
- if (!thread.anchor && !selected.length) throw new Error("A task discussion needs at least one linked task.");
112
+ if (!thread.anchor && !selected.length) throw new Error(t("app.client.threadLinks.taskDiscussionNeedsLinkedTask"));
113
113
  let current = new Set(thread.itemIds);
114
114
  let desired = new Set(selected);
115
115
  let changes = [];
@@ -130,7 +130,7 @@ function planThreadLinks(thread, selected) {
130
130
  });
131
131
  for (let id of added) current.add(id);
132
132
  }
133
- if (!removed.length && !added.length) throw new Error("Could not prepare task links.");
133
+ if (!removed.length && !added.length) throw new Error(t("app.client.threadLinks.couldNotPrepareTaskLinks"));
134
134
  }
135
135
  return changes;
136
136
  }
@@ -147,7 +147,7 @@ async function saveThreadLinks(thread, selected, mutate, signal) {
147
147
  let current = thread;
148
148
  for (let change of changes) {
149
149
  signal.throwIfAborted();
150
- if (!current.revision) throw new Error("Review the latest discussion before changing links.");
150
+ if (!current.revision) throw new Error(t("app.client.threadLinks.reviewLatestDiscussionBeforeChangingLinks"));
151
151
  let input = parseThreadLinkInput({
152
152
  itemIds: change.itemIds,
153
153
  expectedRevision: current.revision
@@ -157,7 +157,7 @@ async function saveThreadLinks(thread, selected, mutate, signal) {
157
157
  let expected = new Set(current.itemIds);
158
158
  for (let id of change.itemIds) if (change.action === "link") expected.add(id);
159
159
  else expected.delete(id);
160
- 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.");
160
+ 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"));
161
161
  current = next;
162
162
  }
163
163
  return current;
@@ -180,7 +180,7 @@ function ProjectThreadLinks(handle) {
180
180
  async function review() {
181
181
  if (!editing) return;
182
182
  let next = await requestComments(routes.comments.get.href({ id: editing.id }), handle.props.project.token, handle.signal, parseThread);
183
- if (next.id !== editing.id) throw new Error("The service returned another discussion.");
183
+ if (next.id !== editing.id) throw new Error(t("app.client.projectThreadLinks.anotherDiscussion"));
184
184
  editing = next;
185
185
  needsReview = false;
186
186
  reviewed = true;
@@ -203,7 +203,7 @@ function ProjectThreadLinks(handle) {
203
203
  }
204
204
  return () => /* @__PURE__ */ jsxs(Fragment, { children: [handle.props.part === "action" ? handle.props.canLink ? /* @__PURE__ */ jsx(IconButton, {
205
205
  icon: "tasks",
206
- label: "Link tasks",
206
+ label: t("app.client.projectThreadLinks.linkTasks"),
207
207
  size: "small",
208
208
  tooltip: true,
209
209
  disabled: handle.props.busy,
@@ -221,7 +221,7 @@ function ProjectThreadLinks(handle) {
221
221
  onClose: close,
222
222
  onReview: needsReview ? review : void 0,
223
223
  onSave: async (selected) => {
224
- if (!editing || needsReview) throw new Error("Review the latest links before saving.");
224
+ if (!editing || needsReview) throw new Error(t("app.client.projectThreadLinks.reviewLinksBeforeSaving"));
225
225
  try {
226
226
  let initial = editing;
227
227
  editing = await saveThreadLinks(initial, selected, (action, input) => {
@@ -1,6 +1,6 @@
1
1
  import { Zt as parseWorkWorkspace, i as routes, y as parseWorkChange } from "./events-6z5v7Uca.js";
2
- import { d as saveWorkspace, l as projectStore, v as projectLive, x as writeRows, y as plainRow } from "./comment-data-BuArfgcc.js";
3
- import { n as requestTasks } from "./tasks-request-Bk5EoY_p.js";
2
+ import { d as saveWorkspace, l as projectStore, v as projectLive, x as writeRows, y as plainRow } from "./comment-data-9SqALQjT.js";
3
+ import { n as requestTasks } from "./tasks-request-C3jk9Jyu.js";
4
4
  //#region app/client/project-work.ts
5
5
  var syncs = /* @__PURE__ */ new Map();
6
6
  /**