@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,5 +1,5 @@
1
- import { $r as PREVIEW_THUMBNAIL_WIDTHS, $t as Button, Ar as parseWorkDetail, Br as valueByRole, Cn as parseThreadLinkInput, Cr as parseElementAnchor, Ct as legacyConversationHref, Dn as parseThreadStatusInput, Dr as parseWorkActivity, Dt as taskHref, En as parseThreadStatus, Er as parseWorkActionInput, Et as selectedTask, Fr as parseWorkSnapshot, G as mergeThreads, Gr as statusCategory, Gt as fileRoutes, Hr as parseMessageAuthor, Ir as parseWorkSync, J as conversationId, Jr as nonempty$1, K as stableJson, Kr as WorkValidationError, Kt as pageRoutes, Lr as parseWorkSyncResult, Mr as parseWorkItemRef, Nr as parseWorkMutationResult, Or as parseWorkConnection, Pr as parseWorkQuery, Q as text$6, Qr as MAX_PREVIEW_HEIGHT, Rr as parseWorkUpdate, S as writeRows, Sn as parseThreadInput, Sr as MAX_CANVAS_Y, Tn as parseThreadSnapshot, Tr as parseSectionData, Tt as readConversationLocation, Ur as fieldByRole, V as parseTaskDesign, Vr as parseMessage, W as CommentMergeError, Wr as parseWorkSchema, X as parseCommentThread, Xr as record$2, Y as object$2, Yr as parseWork, Z as revision$2, Zr as strict$2, Zt as Loader, _ as requestComments, _n as parseAnchor, ai as parsePreviewId, an as LiveStatus, at as FieldCardHeader, b as plainRow, bn as parseThreadFilter, ct as initialShellTabs, d as saveWorkspace, dn as Text, dt as readTabsCookie, ei as PREVIEW_TILE_HEIGHT, ft as stepShellTab, gn as MAX_THREADS, gt as visitShellTab, hn as parseWorkChange, ht as tabsCookieValue, ii as parsePreviewHash, it as FieldCardContent, jr as parseWorkItem, jt as legacyFileHref, kr as parseWorkCreate, l as projectStore, ln as readPreference, lt as openShellTab, mn as parseLiveId, mt as tabsCookieName, n as unavailableComments, ni as parsePreviewBranch, nn as ShellTabBar, oi as parsePreviewPins, ot as activeTab, pn as RESYNC, pt as tabHref, q as parseCommentInput$1, qr as identifier$3, qt as taskRoutes, r as followThreads, ri as parsePreviewCapture, rt as FieldCard, si as parsePreviewViewport, sn as AppShell, st as closeShellTab, t as commentsUnavailable, un as writePreference, ut as parseShellTabs, v as projectLive, vn as parseThread, wn as parseThreadReplyInput, wr as parseWorkWorkspace, xn as parseThreadId, y as routes, yn as parseThreadCapabilities, yt as conversationDestination, zr as parseWorkValue } from "./assets/comment-data-nUlI0szN.js";
2
- import { n as requestTasks } from "./assets/tasks-request-Bwky5ORf.js";
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";
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";
@@ -9,6 +9,7 @@ import * as Crypto from "node:crypto";
9
9
  import { createHash, randomBytes, randomUUID } from "node:crypto";
10
10
  import { any, array, boolean, createSchema, defaulted, enum_, fail, number, object, optional, parse, parseSafe, string } from "remix/data-schema";
11
11
  import { gunzipSync, gzipSync } from "node:zlib";
12
+ import { RoutePattern } from "remix/route-pattern";
12
13
  import { staticFiles } from "remix/middleware/static";
13
14
  import { createController, createRouter } from "remix/router";
14
15
  import * as NodeChildProcess from "node:child_process";
@@ -18,13 +19,13 @@ import * as NFS from "node:fs";
18
19
  import { readFileSync } from "node:fs";
19
20
  import * as OS from "node:os";
20
21
  import { tmpdir } from "node:os";
21
- import { RoutePattern } from "remix/route-pattern";
22
22
  import { clientEntry, navigate, on, ref } from "remix/component";
23
23
  import { jsx, jsxs } from "remix/component/jsx-runtime";
24
24
  import { createMultiMatcher } from "remix/route-pattern/match";
25
25
  import { renderToString } from "remix/component/server";
26
26
  import { createRedirectResponse } from "remix/response/redirect";
27
27
  import { createServer } from "node:net";
28
+ import { AsyncLocalStorage } from "node:async_hooks";
28
29
  //#region ../beads-d1/src/lib/d1.ts
29
30
  /**
30
31
  * A failure whose outcome is known: the statements did not apply. Transports throw it for
@@ -7646,7 +7647,7 @@ var logWithLevel$1 = (level) => (...message) => {
7646
7647
  return void_$2;
7647
7648
  });
7648
7649
  };
7649
- var colors = {
7650
+ var colors$1 = {
7650
7651
  bold: "1",
7651
7652
  red: "31",
7652
7653
  green: "32",
@@ -7658,7 +7659,7 @@ var colors = {
7658
7659
  black: "30",
7659
7660
  bgBrightRed: "101"
7660
7661
  };
7661
- colors.gray, colors.blue, colors.green, colors.yellow, colors.red, colors.bgBrightRed, colors.black;
7662
+ colors$1.gray, colors$1.blue, colors$1.green, colors$1.yellow, colors$1.red, colors$1.bgBrightRed, colors$1.black;
7662
7663
  var defaultDateFormat = (date) => `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}.${date.getMilliseconds().toString().padStart(3, "0")}`;
7663
7664
  /** @internal */
7664
7665
  var defaultLogger = /*#__PURE__*/ loggerMake(({ cause, date, fiber, logLevel, message }) => {
@@ -22149,658 +22150,1409 @@ function httpPreviewStorage(base, options = {}) {
22149
22150
  };
22150
22151
  }
22151
22152
  //#endregion
22152
- //#region ../beads/src/lib/contracts.ts
22153
- var MAX_BEADS = 1e3;
22154
- //#endregion
22155
- //#region ../beads/src/lib/validation.ts
22156
- /** Beads returned or was sent data of the wrong shape; `field` names where. */
22157
- var BeadsValidationError = class extends Error {
22158
- field;
22159
- constructor(field) {
22160
- super(`Invalid Beads data at ${field}.`);
22161
- this.name = "BeadsValidationError";
22162
- this.field = field;
22163
- }
22164
- };
22165
- /**
22166
- * Checks that a value is a JSON object.
22167
- * @param value - Untrusted data.
22168
- * @param field - Where the value sits, for the error.
22169
- * @returns The object.
22170
- */
22171
- function object$1(value, field) {
22172
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new BeadsValidationError(field);
22173
- return value;
22174
- }
22175
- /**
22176
- * Checks a string without NUL characters within a length limit.
22177
- * @param value - Untrusted data.
22178
- * @param field - Where the value sits, for the error.
22179
- * @param max - The most characters it may have.
22180
- * @returns The string.
22181
- */
22182
- function text$4(value, field, max = 262144) {
22183
- if (typeof value !== "string" || value.length > max || value.includes("\0")) throw new BeadsValidationError(field);
22184
- return value;
22185
- }
22186
- /**
22187
- * Checks an ID, name or commit made of letters, digits, dots, underscores and hyphens.
22188
- * @param value - Untrusted data.
22189
- * @param field - Where the value sits, for the error.
22190
- * @returns The identifier.
22191
- */
22192
- function identifier$1(value, field) {
22193
- let result = text$4(value, field, 512);
22194
- if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(result)) throw new BeadsValidationError(field);
22195
- return result;
22196
- }
22197
- /**
22198
- * Checks an optional string.
22199
- * @param value - Untrusted data.
22200
- * @param field - Where the value sits, for the error.
22201
- * @param max - The most characters it may have.
22202
- * @returns The string, or null when it is missing.
22203
- */
22204
- function nullableText(value, field, max) {
22205
- return value == null ? null : text$4(value, field, max);
22206
- }
22153
+ //#region ../project/src/lib/validate.ts
22154
+ var errors = /* @__PURE__ */ new WeakMap();
22207
22155
  /**
22208
- * Checks a nonnegative safe integer.
22209
- * @param value - Untrusted data.
22210
- * @param field - Where the value sits, for the error.
22211
- * @param max - The largest value it may have.
22212
- * @returns The integer.
22156
+ * Validates config, throwing its first problem as the `Error` the config checks report.
22157
+ * @param schema - The config schema.
22158
+ * @param value - The project's untrusted setting.
22159
+ * @returns The validated, normalized setting.
22213
22160
  */
22214
- function integer(value, field, max = Number.MAX_SAFE_INTEGER) {
22215
- if (!Number.isSafeInteger(value) || value < 0 || value > max) throw new BeadsValidationError(field);
22216
- return value;
22161
+ function parseConfig(schema, value) {
22162
+ let result = parseSafe(schema, value, { abortEarly: true });
22163
+ if (result.success) return result.value;
22164
+ let issue = result.issues[0];
22165
+ throw errors.get(issue) ?? new Error(issue.message);
22217
22166
  }
22218
22167
  /**
22219
- * Checks an optional nonnegative integer.
22220
- * @param value - Untrusted data.
22221
- * @param field - Where the value sits, for the error.
22222
- * @param max - The largest value it may have.
22223
- * @returns The integer, or null when it is missing.
22168
+ * Reports any failure of `schema` with one message.
22169
+ * @param schema - The schema to run.
22170
+ * @param message - The message for any of its failures.
22171
+ * @returns The described schema.
22224
22172
  */
22225
- function nullableInteger(value, field, max) {
22226
- return value == null ? null : integer(value, field, max);
22173
+ function described(schema, message) {
22174
+ return createSchema((value, context) => {
22175
+ let result = schema["~run"](value, context);
22176
+ return result.issues ? fail(message, context.path) : result;
22177
+ });
22227
22178
  }
22228
22179
  /**
22229
- * Checks a boolean.
22230
- * @param value - Untrusted data.
22231
- * @param field - Where the value sits, for the error.
22232
- * @returns The boolean.
22180
+ * Applies a rule after `schema` validates.
22181
+ * @param schema - The schema to run first.
22182
+ * @param rule - Returns a message, or undefined when the value is valid.
22183
+ * @returns The checked schema.
22233
22184
  */
22234
- function boolean$2(value, field) {
22235
- if (typeof value !== "boolean") throw new BeadsValidationError(field);
22236
- return value;
22185
+ function check(schema, rule) {
22186
+ return createSchema((value, context) => {
22187
+ let result = schema["~run"](value, context);
22188
+ if (result.issues) return result;
22189
+ let message = rule(result.value);
22190
+ return message === void 0 ? result : fail(message, context.path);
22191
+ });
22237
22192
  }
22238
22193
  /**
22239
- * Checks an ISO 8601 timestamp with a time zone.
22240
- * @param value - Untrusted data.
22241
- * @param field - Where the value sits, for the error.
22242
- * @returns The timestamp as given.
22194
+ * Validates with a function that throws, such as a URL or route map parser, keeping its error.
22195
+ * @param schema - The schema to run first.
22196
+ * @param parse - Turns its value into the output, or throws.
22197
+ * @returns The delegating schema.
22243
22198
  */
22244
- function timestamp(value, field) {
22245
- let result = text$4(value, field, 64);
22246
- if (!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(result) || !Number.isFinite(Date.parse(result))) throw new BeadsValidationError(field);
22247
- return result;
22199
+ function delegate(schema, parse) {
22200
+ return createSchema((value, context) => {
22201
+ let result = schema["~run"](value, context);
22202
+ if (result.issues) return result;
22203
+ try {
22204
+ return { value: parse(result.value) };
22205
+ } catch (error) {
22206
+ if (!(error instanceof Error)) throw error;
22207
+ let failure = fail(error.message, context.path);
22208
+ errors.set(failure.issues[0], error);
22209
+ return failure;
22210
+ }
22211
+ });
22248
22212
  }
22249
22213
  /**
22250
- * Checks an optional ISO 8601 timestamp.
22251
- * @param value - Untrusted data.
22252
- * @param field - Where the value sits, for the error.
22253
- * @returns The timestamp, or null when it is missing.
22214
+ * Chooses a schema from the unvalidated value.
22215
+ * @param choose - Picks the schema for a value.
22216
+ * @returns The selecting schema.
22254
22217
  */
22255
- function nullableTimestamp(value, field) {
22256
- return value == null ? null : timestamp(value, field);
22218
+ function select$1(choose) {
22219
+ return createSchema((value, context) => choose(value)["~run"](value, context));
22257
22220
  }
22258
22221
  /**
22259
- * Checks a bounded array and each of its entries.
22260
- * @param value - Untrusted data.
22261
- * @param field - Where the value sits, for the error.
22262
- * @param parse - Checks one entry.
22263
- * @param max - The most entries it may have.
22264
- * @returns The parsed entries.
22222
+ * Substitutes a default for null or undefined.
22223
+ * @param schema - The schema for present values.
22224
+ * @param value - Makes the default.
22225
+ * @returns The schema with its default.
22265
22226
  */
22266
- function array$1(value, field, parse, max = 1e4) {
22267
- if (!Array.isArray(value) || value.length > max) throw new BeadsValidationError(field);
22268
- return value.map((entry, i) => parse(entry, `${field}[${i}]`));
22227
+ function fallback(schema, value) {
22228
+ return createSchema((input, context) => input == null ? { value: value() } : schema["~run"](input, context));
22269
22229
  }
22270
22230
  /**
22271
- * Checks that no two values share a key.
22272
- * @param values - The values to check.
22273
- * @param key - Gives each value's key.
22274
- * @param field - Where the values sit, for the error.
22275
- * @returns The values.
22231
+ * A settings object that names its unsupported keys before validating its fields.
22232
+ * @param name - The setting's name in messages, such as `previews`.
22233
+ * @param shape - The schema for each supported key.
22234
+ * @returns The settings schema.
22276
22235
  */
22277
- function unique(values, key, field) {
22278
- if (new Set(values.map(key)).size !== values.length) throw new BeadsValidationError(field);
22279
- return values;
22236
+ function settings(name, shape) {
22237
+ let schema = object(shape);
22238
+ return createSchema((value, context) => {
22239
+ if (!value || typeof value !== "object" || Array.isArray(value)) return fail(`${name} must be an object.`, context.path);
22240
+ for (let key of Object.keys(value)) if (!Object.hasOwn(shape, key)) return fail(`${name}.${key} is not a supported setting.`, [...context.path, key]);
22241
+ return schema["~run"](value, context);
22242
+ });
22280
22243
  }
22281
22244
  /**
22282
- * Checks JSON metadata, bounding its depth and its total number of values.
22283
- * @param value - Untrusted data.
22284
- * @param field - Where the value sits, for the error.
22285
- * @param depth - How deep this value is nested.
22286
- * @param budget - The values left to check, shared across the whole tree.
22287
- * @returns The metadata.
22245
+ * A list whose length is checked before its entries.
22246
+ * @param item - The schema for each entry.
22247
+ * @param message - The message when it is not a list or is too short.
22248
+ * @param minimum - The fewest entries it may have.
22249
+ * @returns The list schema.
22288
22250
  */
22289
- function metadata(value, field, depth = 0, budget = { remaining: 1e4 }) {
22290
- if (depth > 12 || --budget.remaining < 0) throw new BeadsValidationError(field);
22291
- if (value === null || typeof value === "boolean") return value;
22292
- if (typeof value === "number" && Number.isFinite(value)) return value;
22293
- if (typeof value === "string") return text$4(value, field);
22294
- if (Array.isArray(value)) return value.map((entry, index) => metadata(entry, `${field}[${index}]`, depth + 1, budget));
22295
- let input = object$1(value, field);
22296
- return Object.fromEntries(Object.entries(input).map(([key, entry]) => [text$4(key, field, 512), metadata(entry, `${field}.${key}`, depth + 1, budget)]));
22251
+ function list(item, message, minimum = 0) {
22252
+ let items = array(item);
22253
+ return createSchema((value, context) => !Array.isArray(value) || value.length < minimum ? fail(message, context.path) : items["~run"](value, context));
22297
22254
  }
22298
- function dependency(value, field) {
22299
- let input = object$1(value, field);
22255
+ //#endregion
22256
+ //#region ../project/src/lib/files.ts
22257
+ /** The Files settings, with their defaults filled in. */
22258
+ var BudFilesSchema = defaulted(settings("files", { edit: fallback(described(boolean(), "files.edit must be true or false."), () => true) }), () => ({ edit: true }));
22259
+ //#endregion
22260
+ //#region ../project/src/lib/flows.ts
22261
+ var message$1 = "flows must be [{ id, title, start, script, steps: [{ id, title }], block? }] with short ids, local paths and unique steps.";
22262
+ var slug = (value) => typeof value === "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(value);
22263
+ var text$4 = (value) => typeof value === "string" && value.trim() !== "" && value.length <= 200;
22264
+ function flow(value) {
22265
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
22266
+ let { id, title, start, script, steps, block = [] } = value;
22267
+ if (!slug(id) || !text$4(title) || !text$4(script) || script.includes("\0")) return null;
22268
+ if (typeof start !== "string" || !start.startsWith("/") || start.startsWith("//")) return null;
22269
+ if (new URL(start, "http://bud.invalid").origin !== "http://bud.invalid") return null;
22270
+ if (!Array.isArray(steps) || !steps.length || steps.length > 50) return null;
22271
+ let resolved = steps.map((step) => {
22272
+ let { id: stepId, title: stepTitle } = step ?? {};
22273
+ return slug(stepId) && text$4(stepTitle) ? {
22274
+ id: stepId,
22275
+ title: stepTitle.trim()
22276
+ } : null;
22277
+ });
22278
+ if (resolved.some((step) => !step)) return null;
22279
+ let ids = resolved.map((step) => step.id);
22280
+ if (new Set(ids).size !== ids.length) return null;
22281
+ if (!Array.isArray(block) || block.some((part) => !text$4(part))) return null;
22300
22282
  return {
22301
- id: identifier$1(input.id, `${field}.id`),
22302
- type: identifier$1(input.type, `${field}.type`),
22303
- title: nullableText(input.title, `${field}.title`),
22304
- status: nullableText(input.status, `${field}.status`, 128),
22305
- priority: nullableInteger(input.priority, `${field}.priority`, 4),
22306
- issueType: nullableText(input.issueType, `${field}.issueType`, 128)
22283
+ id,
22284
+ title: title.trim(),
22285
+ start,
22286
+ script: script.trim(),
22287
+ steps: resolved,
22288
+ block
22307
22289
  };
22308
22290
  }
22291
+ /** The flows, none when omitted. */
22292
+ var BudFlowsSchema = createSchema((value, context) => {
22293
+ if (value === void 0) return { value: [] };
22294
+ if (!Array.isArray(value)) return fail(message$1, context.path);
22295
+ let flows = value.map(flow);
22296
+ if (flows.some((resolved) => !resolved)) return fail(message$1, context.path);
22297
+ let ids = flows.map((resolved) => resolved.id);
22298
+ if (new Set(ids).size !== ids.length) return fail(message$1, context.path);
22299
+ return { value: flows };
22300
+ });
22309
22301
  /**
22310
- * Validates an issue.
22311
- * @param value - Untrusted data.
22312
- * @param field - Where the value sits, for the error.
22313
- * @returns The issue.
22302
+ * Where a flow's step is pictured: a route of its own, named `<flow>/<step>`, in a group named
22303
+ * after the flow. Its path is the start page with the step as its fragment, unique among routes.
22304
+ * @param flow - The flow.
22305
+ * @param step - One of its steps.
22306
+ * @returns The step's route.
22314
22307
  */
22315
- function parseBeadIssue(value, field = "issue") {
22316
- let input = object$1(value, field);
22317
- let string = (key, max) => text$4(input[key], `${field}.${key}`, max);
22318
- let optional = (key, max) => nullableText(input[key], `${field}.${key}`, max);
22319
- let count = (key) => nullableInteger(input[key], `${field}.${key}`);
22320
- let date = (key) => nullableTimestamp(input[key], `${field}.${key}`);
22308
+ function flowStepRoute(flow, step) {
22321
22309
  return {
22322
- id: identifier$1(input.id, `${field}.id`),
22323
- title: string("title"),
22324
- status: identifier$1(input.status, `${field}.status`),
22325
- priority: integer(input.priority, `${field}.priority`, 4),
22326
- type: identifier$1(input.type, `${field}.type`),
22327
- description: string("description"),
22328
- design: string("design"),
22329
- acceptanceCriteria: string("acceptanceCriteria"),
22330
- notes: string("notes"),
22331
- assignee: optional("assignee"),
22332
- owner: optional("owner"),
22333
- createdBy: optional("createdBy"),
22334
- createdAt: timestamp(input.createdAt, `${field}.createdAt`),
22335
- updatedAt: timestamp(input.updatedAt, `${field}.updatedAt`),
22336
- startedAt: date("startedAt"),
22337
- closedAt: date("closedAt"),
22338
- closeReason: optional("closeReason"),
22339
- dueAt: date("dueAt"),
22340
- deferUntil: date("deferUntil"),
22341
- estimatedMinutes: count("estimatedMinutes"),
22342
- externalRef: optional("externalRef"),
22343
- specId: optional("specId"),
22344
- labels: unique(array$1(input.labels, `${field}.labels`, (item, path) => text$4(item, path, 512), 1e3), (item) => item, `${field}.labels`),
22345
- parentId: input.parentId == null ? null : identifier$1(input.parentId, `${field}.parentId`),
22346
- dependencies: array$1(input.dependencies, `${field}.dependencies`, dependency),
22347
- dependents: array$1(input.dependents, `${field}.dependents`, dependency),
22348
- dependencyCount: count("dependencyCount"),
22349
- dependentCount: count("dependentCount"),
22350
- commentCount: count("commentCount"),
22351
- epicTotalChildren: count("epicTotalChildren"),
22352
- epicClosedChildren: count("epicClosedChildren"),
22353
- epicCloseable: input.epicCloseable == null ? null : boolean$2(input.epicCloseable, `${field}.epicCloseable`),
22354
- revision: optional("revision", 256),
22355
- metadata: metadata(input.metadata, `${field}.metadata`),
22356
- contentLoaded: boolean$2(input.contentLoaded, `${field}.contentLoaded`)
22310
+ id: `${flow.id}/${step.id}`,
22311
+ path: `${flow.start.replace(/#.*$/, "")}#${flow.id}-${step.id}`,
22312
+ title: step.title,
22313
+ group: flow.title,
22314
+ flow: {
22315
+ id: flow.id,
22316
+ step: step.id
22317
+ }
22357
22318
  };
22358
22319
  }
22359
- /**
22360
- * Validates a connection state.
22361
- * @param value - Untrusted data.
22362
- * @returns The connection.
22363
- */
22364
- function parseBeadsConnection(value) {
22365
- let input = object$1(value, "connection");
22366
- if (input.state === "ready") {
22367
- let project = object$1(input.project, "connection.project");
22368
- if (input.schemaVersion !== 1) throw new BeadsValidationError("connection.schemaVersion");
22369
- return {
22370
- state: "ready",
22371
- version: text$4(input.version, "connection.version", 128),
22372
- schemaVersion: 1,
22373
- project: {
22374
- name: text$4(project.name, "project.name", 512),
22375
- prefix: nullableText(project.prefix, "project.prefix", 512),
22376
- backend: text$4(project.backend, "project.backend", 128),
22377
- mode: nullableText(project.mode, "project.mode", 128),
22378
- redirected: boolean$2(project.redirected, "project.redirected"),
22379
- worktree: boolean$2(project.worktree, "project.worktree")
22380
- }
22381
- };
22320
+ //#endregion
22321
+ //#region ../project/src/lib/logo.ts
22322
+ /** The image types a logo may be, by extension. */
22323
+ var LOGO_TYPES = Object.freeze({
22324
+ ".svg": "image/svg+xml",
22325
+ ".png": "image/png",
22326
+ ".webp": "image/webp",
22327
+ ".jpg": "image/jpeg",
22328
+ ".jpeg": "image/jpeg",
22329
+ ".gif": "image/gif",
22330
+ ".ico": "image/x-icon"
22331
+ });
22332
+ var logoMessage = "logo must be an image path inside the project (.svg, .png, .webp, .jpg, .gif or .ico), or { light, dark }.";
22333
+ function image(value) {
22334
+ if (typeof value !== "string" || value.length > 1024 || value.includes("\0")) return null;
22335
+ let path = value.trim().replaceAll("\\", "/");
22336
+ if (!path || path.startsWith("/") || /^[a-z]:/i.test(path)) return null;
22337
+ if (path.split("/").includes("..")) return null;
22338
+ let extension = path.slice(path.lastIndexOf(".")).toLowerCase();
22339
+ return Object.hasOwn(LOGO_TYPES, extension) ? path : null;
22340
+ }
22341
+ /** A logo in its one resolved form, or null for Bud's own mark. */
22342
+ var BudLogoSchema = createSchema((value, context) => {
22343
+ if (value === void 0) return { value: null };
22344
+ let light = image(typeof value === "object" && value ? value.light : value);
22345
+ let raw = typeof value === "object" && value ? value.dark : void 0;
22346
+ let dark = raw === void 0 ? null : image(raw);
22347
+ let keys = typeof value === "object" && value ? Object.keys(value) : [];
22348
+ if (light && (raw === void 0 || dark) && keys.every((key) => key === "light" || key === "dark")) return { value: {
22349
+ light,
22350
+ dark
22351
+ } };
22352
+ return fail(logoMessage, context.path);
22353
+ });
22354
+ //#endregion
22355
+ //#region ../project/src/lib/theme.ts
22356
+ /**
22357
+ * Bud's colors and shadows a theme may set, as `--bud-<name>` in its styles. Everything else
22358
+ * Bud derives from them.
22359
+ */
22360
+ var THEME_TOKENS = Object.freeze([
22361
+ "background",
22362
+ "shell",
22363
+ "main",
22364
+ "canvas",
22365
+ "panel",
22366
+ "board",
22367
+ "editor",
22368
+ "editor-tabs",
22369
+ "foreground",
22370
+ "muted",
22371
+ "primary",
22372
+ "primary-hover",
22373
+ "primary-foreground",
22374
+ "border",
22375
+ "tab-divider",
22376
+ "hover",
22377
+ "selected",
22378
+ "control",
22379
+ "control-active",
22380
+ "control-selected",
22381
+ "card",
22382
+ "menu",
22383
+ "menu-hover",
22384
+ "card-frame",
22385
+ "card-surface",
22386
+ "card-border",
22387
+ "card-frame-border",
22388
+ "card-badge",
22389
+ "overlay-shadow",
22390
+ "tooltip",
22391
+ "board-control",
22392
+ "board-control-hover",
22393
+ "board-card",
22394
+ "control-elevation",
22395
+ "board-elevation",
22396
+ "board-card-elevation",
22397
+ "card-elevation",
22398
+ "board-card-lift",
22399
+ "frame-control",
22400
+ "modal-elevation",
22401
+ "segment-elevation",
22402
+ "control-open-elevation",
22403
+ "board-segment",
22404
+ "scrollbar",
22405
+ "focus",
22406
+ "scrollbar-hover",
22407
+ "tooltip-border",
22408
+ "tooltip-shadow"
22409
+ ]);
22410
+ var themeMessage = `theme must be { light, dark }, each mapping Bud's tokens (${THEME_TOKENS.slice(0, 3).join(", ")}, …) to CSS colors or shadows.`;
22411
+ function cssValue(value) {
22412
+ if (typeof value !== "string") return null;
22413
+ let trimmed = value.trim();
22414
+ if (!trimmed || trimmed.length > 300) return null;
22415
+ if (!/^[#a-zA-Z0-9(),.%\s/+-]+$/.test(trimmed) || /url\s*\(/i.test(trimmed)) return null;
22416
+ return trimmed;
22417
+ }
22418
+ function colors(value) {
22419
+ if (value === void 0) return {};
22420
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
22421
+ let result = {};
22422
+ for (let [name, raw] of Object.entries(value)) {
22423
+ let checked = cssValue(raw);
22424
+ if (!THEME_TOKENS.includes(name) || checked === null) return null;
22425
+ result[name] = checked;
22382
22426
  }
22383
- if (![
22384
- "unavailable",
22385
- "not-initialized",
22386
- "unsupported-version"
22387
- ].includes(input.state)) throw new BeadsValidationError("connection.state");
22388
- return {
22389
- state: input.state,
22390
- version: nullableText(input.version, "connection.version", 128),
22391
- message: text$4(input.message, "connection.message", 1e3)
22392
- };
22427
+ return Object.freeze(result);
22393
22428
  }
22394
- /**
22395
- * Validates a status definition.
22396
- * @param value - Untrusted data.
22397
- * @param field - Where the value sits, for the error.
22398
- * @returns The status.
22399
- */
22400
- function parseBeadStatus(value, field = "status") {
22401
- let input = object$1(value, field);
22402
- return {
22403
- name: identifier$1(input.name, `${field}.name`),
22404
- category: nullableText(input.category, `${field}.category`, 128),
22405
- description: text$4(input.description, `${field}.description`),
22406
- custom: boolean$2(input.custom, `${field}.custom`)
22429
+ /** A theme in its one resolved form; Bud's own colors when absent. */
22430
+ var BudThemeSchema = createSchema((value, context) => {
22431
+ if (value === void 0) return { value: {
22432
+ light: {},
22433
+ dark: {}
22434
+ } };
22435
+ if (!value || typeof value !== "object" || Array.isArray(value)) return fail(themeMessage, context.path);
22436
+ let { light, dark, ...rest } = value;
22437
+ let resolved = {
22438
+ light: colors(light),
22439
+ dark: colors(dark)
22407
22440
  };
22408
- }
22441
+ if (Object.keys(rest).length || !resolved.light || !resolved.dark) return fail(themeMessage, context.path);
22442
+ return { value: {
22443
+ light: resolved.light,
22444
+ dark: resolved.dark
22445
+ } };
22446
+ });
22409
22447
  /**
22410
- * Validates an issue type definition.
22411
- * @param value - Untrusted data.
22412
- * @param field - Where the value sits, for the error.
22413
- * @returns The type.
22448
+ * The theme as styles that follow Bud's own, so its tokens win in each color scheme.
22449
+ * @param theme - The resolved theme.
22450
+ * @returns A `<style>` element, or an empty string for Bud's own colors.
22414
22451
  */
22415
- function parseBeadType(value, field = "type") {
22416
- let input = object$1(value, field);
22417
- if (![
22418
- "core",
22419
- "system",
22420
- "custom"
22421
- ].includes(input.kind)) throw new BeadsValidationError(`${field}.kind`);
22422
- return {
22423
- name: identifier$1(input.name, `${field}.name`),
22424
- description: text$4(input.description, `${field}.description`),
22425
- kind: input.kind
22452
+ function themeStyle(theme) {
22453
+ let block = (selector, values) => {
22454
+ let entries = Object.entries(values);
22455
+ if (!entries.length) return "";
22456
+ return `${selector}{${entries.map(([name, value]) => `--bud-${name}:${value};`).join("")}}`;
22426
22457
  };
22458
+ let css = block(":root:not([data-theme='dark'])", theme.light) + block(":root[data-theme='dark']", theme.dark);
22459
+ return css ? `<style data-bud-theme>${css}</style>` : "";
22427
22460
  }
22428
- /**
22429
- * Validates a board snapshot, which holds no rows unless its connection is ready.
22430
- * @param value - Untrusted data.
22431
- * @returns The snapshot.
22432
- */
22433
- function parseBeadsSnapshot(value) {
22434
- let input = object$1(value, "snapshot");
22435
- let connection = parseBeadsConnection(input.connection);
22436
- let issues = unique(array$1(input.issues, "snapshot.issues", parseBeadIssue, MAX_BEADS), (issue) => issue.id, "snapshot.issues");
22437
- let readyIds = unique(array$1(input.readyIds, "snapshot.readyIds", identifier$1, MAX_BEADS), (id) => id, "snapshot.readyIds");
22438
- let statuses = unique(array$1(input.statuses, "snapshot.statuses", parseBeadStatus, 1e3), (item) => item.name, "snapshot.statuses");
22439
- let types = unique(array$1(input.types, "snapshot.types", parseBeadType, 1e3), (item) => item.name, "snapshot.types");
22440
- let truncated = boolean$2(input.truncated, "snapshot.truncated");
22441
- if (connection.state !== "ready" && (issues.length || readyIds.length || statuses.length || types.length || truncated)) throw new BeadsValidationError("snapshot.connection");
22442
- return {
22443
- connection,
22444
- issues,
22445
- readyIds,
22446
- statuses,
22447
- types,
22448
- truncated
22449
- };
22461
+ //#endregion
22462
+ //#region ../project/src/lib/branches.ts
22463
+ var message = "branches must be { main?, build?: { install?, build?, serve? } } with a branch name and nonempty shell commands.";
22464
+ function branch(value) {
22465
+ return typeof value === "string" && value.length <= 255 && /^[A-Za-z0-9._/-]+$/.test(value) && !value.startsWith("-") && !value.startsWith("/") && !value.endsWith("/") && !value.endsWith(".lock") && !value.includes("..") && !value.includes("//") ? value : null;
22450
22466
  }
22451
- /**
22452
- * Validates an issue comment.
22453
- * @param value - Untrusted data.
22454
- * @param field - Where the value sits, for the error.
22455
- * @returns The comment.
22456
- */
22457
- function parseBeadDiscussion(value, field = "discussion") {
22458
- let input = object$1(value, field);
22459
- return {
22460
- id: identifier$1(input.id, `${field}.id`),
22461
- issueId: identifier$1(input.issueId, `${field}.issueId`),
22462
- author: text$4(input.author, `${field}.author`),
22463
- body: text$4(input.body, `${field}.body`),
22464
- createdAt: timestamp(input.createdAt, `${field}.createdAt`)
22465
- };
22467
+ function command(value) {
22468
+ return typeof value === "string" && value.trim() && value.length <= 4096 && !value.includes("\0") ? value.trim() : null;
22466
22469
  }
22467
- /**
22468
- * Validates an issue's version at one commit.
22469
- * @param value - Untrusted data.
22470
- * @param field - Where the value sits, for the error.
22471
- * @returns The version.
22472
- */
22473
- function parseBeadHistory(value, field = "history") {
22474
- let input = object$1(value, field);
22475
- return {
22476
- commit: identifier$1(input.commit, `${field}.commit`),
22477
- author: text$4(input.author, `${field}.author`),
22478
- createdAt: timestamp(input.createdAt, `${field}.createdAt`),
22479
- issue: input.issue == null ? null : parseBeadIssue(input.issue, `${field}.issue`)
22470
+ /** The branch settings, defaults filled in. */
22471
+ var BudBranchesSchema = createSchema((value, context) => {
22472
+ let empty = {
22473
+ main: null,
22474
+ install: null,
22475
+ build: null,
22476
+ serve: null
22480
22477
  };
22481
- }
22478
+ if (value === void 0) return { value: empty };
22479
+ if (!value || typeof value !== "object" || Array.isArray(value)) return fail(message, context.path);
22480
+ let input = value;
22481
+ let build = input.build ?? {};
22482
+ if (!build || typeof build !== "object" || Array.isArray(build) || Object.keys(input).some((key) => key !== "main" && key !== "build")) return fail(message, context.path);
22483
+ let steps = build;
22484
+ if (Object.keys(steps).some((key) => ![
22485
+ "install",
22486
+ "build",
22487
+ "serve"
22488
+ ].includes(key))) return fail(message, context.path);
22489
+ let resolved = {
22490
+ main: input.main === void 0 ? null : branch(input.main),
22491
+ install: steps.install === void 0 ? null : command(steps.install),
22492
+ build: steps.build === void 0 ? null : command(steps.build),
22493
+ serve: steps.serve === void 0 ? null : command(steps.serve)
22494
+ };
22495
+ if (input.main !== void 0 && !resolved.main || [
22496
+ "install",
22497
+ "build",
22498
+ "serve"
22499
+ ].some((key) => steps[key] !== void 0 && !resolved[key])) return fail(message, context.path);
22500
+ return { value: resolved };
22501
+ });
22502
+ //#endregion
22503
+ //#region ../project/src/lib/storage.ts
22482
22504
  /**
22483
- * Validates an event log entry.
22484
- * @param value - Untrusted data.
22485
- * @param field - Where the value sits, for the error.
22486
- * @returns The event.
22505
+ * A project module named in the config: a ./ path inside the project, outside node_modules.
22506
+ * @param name - The config option, for the error message.
22507
+ * @param server - Whether it runs on the server (.server.ts) rather than in the browser.
22508
+ * @param message - What to say instead of the default when it is invalid.
22509
+ * @returns A schema for the module's path.
22487
22510
  */
22488
- function parseBeadEvent(value, field = "event") {
22489
- let input = object$1(value, field);
22490
- return {
22491
- id: identifier$1(input.id, `${field}.id`),
22492
- issueId: identifier$1(input.issueId, `${field}.issueId`),
22493
- type: identifier$1(input.type, `${field}.type`),
22494
- actor: text$4(input.actor, `${field}.actor`),
22495
- oldValue: nullableText(input.oldValue, `${field}.oldValue`),
22496
- newValue: nullableText(input.newValue, `${field}.newValue`),
22497
- comment: nullableText(input.comment, `${field}.comment`),
22498
- createdAt: timestamp(input.createdAt, `${field}.createdAt`)
22499
- };
22511
+ function modulePath(name, server, message) {
22512
+ let extension = server ? /\.server\.ts$/ : /\.(?:ts|tsx|js|jsx)$/;
22513
+ return described(string().refine((value) => value.length <= 1024 && value.startsWith("./") && !/[\u0000-\u001f\u007f\\?#%]/.test(value) && extension.test(value) && (server || !/\.server\.[cm]?[jt]sx?$/.test(value)) && !value.slice(2).split("/").some((part) => !part || part === "." || part === ".." || part === "node_modules")), message ?? (server ? `${name} must be a built-in adapter or a ./ module ending in .server.ts inside the project.` : `${name} must be bud:beads or a ./ browser module ending in .ts, .tsx, .js or .jsx inside the project.`));
22500
22514
  }
22501
- function section$3(value, field, parser, max) {
22502
- let input = object$1(value, field);
22503
- let items = array$1(input.items, `${field}.items`, parser, max);
22504
- let truncated = boolean$2(input.truncated, `${field}.truncated`);
22505
- if (input.state === "available") return {
22506
- state: "available",
22507
- items,
22508
- truncated
22509
- };
22510
- if (input.state === "unsupported" && items.length === 0 && truncated === false) return {
22511
- state: "unsupported",
22512
- items: [],
22513
- truncated: false
22514
- };
22515
- throw new BeadsValidationError(`${field}.state`);
22516
- }
22517
- /**
22518
- * Validates an issue's detail, whose sections must all belong to that issue.
22519
- * @param value - Untrusted data.
22520
- * @returns The detail.
22521
- */
22522
- function parseBeadDetail(value) {
22523
- let input = object$1(value, "detail");
22524
- let connection = parseBeadsConnection(input.connection);
22525
- if (connection.state !== "ready") throw new BeadsValidationError("detail.connection");
22526
- let issue = parseBeadIssue(input.issue);
22527
- let discussion = section$3(input.discussion, "discussion", parseBeadDiscussion, 200);
22528
- let history = section$3(input.history, "history", parseBeadHistory, 50);
22529
- let events = section$3(input.events, "events", parseBeadEvent, 50);
22530
- if (!issue.contentLoaded || discussion.items.some((entry) => entry.issueId !== issue.id) || events.items.some((entry) => entry.issueId !== issue.id) || history.items.some((entry) => entry.issue && entry.issue.id !== issue.id)) throw new BeadsValidationError("detail.issue");
22531
- return {
22532
- connection,
22533
- issue,
22534
- discussion,
22535
- history,
22536
- events
22537
- };
22515
+ var builtInStores = enum_([
22516
+ false,
22517
+ "bud:beads",
22518
+ "bud:memory",
22519
+ "bud:git-comments"
22520
+ ]);
22521
+ function store(kind) {
22522
+ let module = modulePath(`storage.${kind}`, true);
22523
+ return defaulted(check(select$1((value) => value === false || value === "bud:beads" || value === "bud:memory" || value === "bud:git-comments" ? builtInStores : module), (value) => kind === "work" && value === "bud:git-comments" ? "storage.work cannot use bud:git-comments; it only stores threads." : void 0), "bud:beads");
22538
22524
  }
22525
+ /** The selected work and discussion stores, with Beads as the default for both. */
22526
+ var BudStorageSchema = defaulted(settings("storage", {
22527
+ work: store("work"),
22528
+ threads: store("threads")
22529
+ }), () => ({
22530
+ work: "bud:beads",
22531
+ threads: "bud:beads"
22532
+ }));
22533
+ /** Task presentation, chosen separately from its storage. */
22534
+ var BudDesignsSchema = defaulted(settings("designs", {
22535
+ tasks: defaulted(select$1((value) => value === "bud:beads" ? enum_(["bud:beads"]) : modulePath("designs.tasks", false)), "bud:beads"),
22536
+ pages: defaulted(enum_(["path", "title"]), "path")
22537
+ }), () => ({
22538
+ tasks: "bud:beads",
22539
+ pages: "path"
22540
+ }));
22539
22541
  //#endregion
22540
- //#region ../beads/src/lib/actions.ts
22541
- var dependencyTypes = [
22542
- "blocks",
22543
- "tracks",
22544
- "related",
22545
- "discovered-from",
22546
- "until",
22547
- "caused-by",
22548
- "validates",
22549
- "relates-to",
22550
- "supersedes"
22551
- ];
22552
- var fields$2 = [
22553
- "title",
22554
- "type",
22555
- "status",
22556
- "priority",
22557
- "assignee",
22558
- "labels",
22559
- "parentId",
22560
- "description",
22561
- "design",
22562
- "acceptanceCriteria",
22563
- "notes",
22564
- "estimatedMinutes",
22565
- "externalRef",
22566
- "specId",
22567
- "dueAt",
22568
- "deferUntil",
22569
- "metadata"
22570
- ];
22571
- function strict$1(value, name, allowed) {
22572
- let input = object$1(value, name);
22573
- for (let key of Object.keys(input)) if (!allowed.includes(key)) throw new BeadsValidationError(`${name}.${key}`);
22574
- return input;
22575
- }
22576
- function nonempty(value, field, max) {
22577
- let result = text$4(value, field, max).trim();
22578
- if (!result) throw new BeadsValidationError(field);
22579
- return result;
22542
+ //#region ../project/src/lib/picker.ts
22543
+ var builtIn = /* @__PURE__ */ new Set([
22544
+ "bud:dom",
22545
+ "bud:react",
22546
+ "bud:react-grab"
22547
+ ]);
22548
+ var projectPicker = modulePath("picker", false, "picker must be bud:dom, bud:react, bud:react-grab or a ./ browser module ending in .ts, .tsx, .js or .jsx inside the project.");
22549
+ /** The picker, with Bud's DOM picker as the default. */
22550
+ var BudPickerSchema = createSchema((value, context) => {
22551
+ if (value === void 0) return { value: {
22552
+ name: "bud:dom",
22553
+ module: null
22554
+ } };
22555
+ if (typeof value === "string" && builtIn.has(value)) return { value: {
22556
+ name: value,
22557
+ module: null
22558
+ } };
22559
+ let result = projectPicker["~run"](value, context);
22560
+ return result.issues ? result : { value: {
22561
+ name: result.value,
22562
+ module: result.value
22563
+ } };
22564
+ });
22565
+ //#endregion
22566
+ //#region ../project/src/lib/previews.ts
22567
+ var readyMessage = "ready must be a CSS selector, { selector } or { expression }.";
22568
+ /** A readiness condition; a string is a selector. */
22569
+ var PreviewReadySchema = createSchema((value, context) => {
22570
+ let ready = typeof value === "string" ? { selector: value } : value;
22571
+ if (ready && typeof ready === "object" && !Array.isArray(ready)) {
22572
+ let entries = Object.entries(ready);
22573
+ let [key, text] = entries[0] ?? [];
22574
+ if (entries.length === 1 && (key === "selector" || key === "expression") && typeof text === "string" && text.trim() && text.length <= 4096) return { value: key === "selector" ? { selector: text.trim() } : { expression: text.trim() } };
22575
+ }
22576
+ return fail(readyMessage, context.path);
22577
+ });
22578
+ var defaultThemes = () => ["light", "dark"];
22579
+ var defaultViewports = () => [{
22580
+ id: "desktop",
22581
+ name: "Desktop",
22582
+ width: 1440,
22583
+ height: 900,
22584
+ mobile: false
22585
+ }];
22586
+ var viewportsMessage = "previews.viewports must list up to 4 { name, width, height?, mobile? } with distinct names, widths from 240 to 3840 and heights from 240 to 4000.";
22587
+ function viewportId(name) {
22588
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
22580
22589
  }
22590
+ var ViewportsSchema = createSchema((value, context) => {
22591
+ if (!Array.isArray(value) || value.length === 0 || value.length > 4) return fail(viewportsMessage, context.path);
22592
+ let viewports = [];
22593
+ for (let entry of value) {
22594
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return fail(viewportsMessage, context.path);
22595
+ let { name, width, height, mobile, ...rest } = entry;
22596
+ let size = (pixels, from, to) => typeof pixels === "number" && Number.isInteger(pixels) && pixels >= from && pixels <= to;
22597
+ if (Object.keys(rest).length || typeof name !== "string" || !name.trim() || name.length > 40 || !viewportId(name) || !size(width, 240, 3840) || height !== void 0 && !size(height, 240, 4e3) || mobile !== void 0 && typeof mobile !== "boolean") return fail(viewportsMessage, context.path);
22598
+ let phone = mobile ?? width < 800;
22599
+ viewports.push({
22600
+ id: viewportId(name),
22601
+ name: name.trim(),
22602
+ width,
22603
+ height: height ?? (phone ? Math.round(width * 844 / 390) : 900),
22604
+ mobile: phone
22605
+ });
22606
+ }
22607
+ if (new Set(viewports.map((viewport) => viewport.id)).size !== viewports.length) return fail(viewportsMessage, context.path);
22608
+ return { value: viewports };
22609
+ });
22610
+ /** The preview settings, with their defaults filled in. */
22611
+ var BudPreviewsSchema = defaulted(settings("previews", {
22612
+ scale: fallback(described(number().refine((scale) => Number.isInteger(scale) && scale >= 1 && scale <= 3), "previews.scale must be 1, 2 or 3."), () => 2),
22613
+ themes: fallback(described(array(enum_(["light", "dark"])).refine((themes) => themes.length > 0 && new Set(themes).size === themes.length), "previews.themes must list light, dark or both, once each."), defaultThemes),
22614
+ viewports: fallback(ViewportsSchema, defaultViewports),
22615
+ ready: fallback(PreviewReadySchema, () => null),
22616
+ readyTimeout: fallback(described(number().refine((ms) => Number.isInteger(ms) && ms >= 1e3 && ms <= 6e5), "previews.readyTimeout must be whole milliseconds from 1000 to 600000."), () => 6e4),
22617
+ css: fallback(described(string().refine((css) => css.length <= 2e4 && !css.includes("<")), "previews.css must be CSS text without <, up to 20000 characters."), () => "")
22618
+ }), () => ({
22619
+ scale: 2,
22620
+ themes: defaultThemes(),
22621
+ viewports: defaultViewports(),
22622
+ ready: null,
22623
+ readyTimeout: 6e4,
22624
+ css: ""
22625
+ }));
22626
+ //#endregion
22627
+ //#region ../project/src/lib/screen-routes.ts
22581
22628
  /**
22582
- * Validates an update's fields, rejecting unknown and oversized ones.
22583
- * @param value - Untrusted data.
22584
- * @returns The patch.
22629
+ * Validates a screen's route map without importing its component module.
22630
+ * @param routes - A Remix route map shared with the screen's definition.
22631
+ * @param path - The configured screen's static path.
22632
+ * @returns Named patterns confined to the screen's path.
22585
22633
  */
22586
- function parseBeadPatch(value) {
22587
- let input = strict$1(value, "patch", fields$2);
22588
- let patch = {};
22589
- if (!Object.keys(input).length) throw new BeadsValidationError("patch");
22590
- if ("title" in input) patch.title = nonempty(input.title, "patch.title", 1e3);
22591
- if ("type" in input) patch.type = identifier$1(input.type, "patch.type");
22592
- if ("status" in input) patch.status = identifier$1(input.status, "patch.status");
22593
- if ("priority" in input) patch.priority = integer(input.priority, "patch.priority", 4);
22594
- if ("assignee" in input) patch.assignee = text$4(input.assignee, "patch.assignee", 256).trim();
22595
- if ("labels" in input) patch.labels = unique(array$1(input.labels, "patch.labels", (value, field) => {
22596
- let label = nonempty(value, field, 128);
22597
- if (/[\r\n,]/.test(label)) throw new BeadsValidationError(field);
22598
- return label;
22599
- }, 100), (label) => label, "patch.labels");
22600
- if ("parentId" in input) patch.parentId = input.parentId === null ? null : identifier$1(input.parentId, "patch.parentId");
22601
- for (let key of [
22602
- "description",
22603
- "design",
22604
- "acceptanceCriteria",
22605
- "notes"
22606
- ]) if (key in input) patch[key] = text$4(input[key], `patch.${key}`, 6e4);
22607
- if ("estimatedMinutes" in input) patch.estimatedMinutes = integer(input.estimatedMinutes, "patch.estimatedMinutes", 1e6);
22608
- for (let key of ["externalRef", "specId"]) if (key in input) patch[key] = text$4(input[key], `patch.${key}`, 2048);
22609
- for (let key of ["dueAt", "deferUntil"]) if (key in input) patch[key] = text$4(input[key], `patch.${key}`, 128);
22610
- if ("metadata" in input) {
22611
- let source = object$1(input.metadata, "patch.metadata");
22612
- if (JSON.stringify(source).length > 32e3) throw new BeadsValidationError("patch.metadata");
22613
- patch.metadata = metadata(source, "patch.metadata");
22634
+ function resolveScreenRoutes(routes, path) {
22635
+ let result = [];
22636
+ let seen = /* @__PURE__ */ new Set();
22637
+ function visit(value, prefix) {
22638
+ if (!value || typeof value !== "object" || Array.isArray(value) || seen.has(value)) throw new Error("Screen routes must be a Remix route map.");
22639
+ seen.add(value);
22640
+ for (let [key, entry] of Object.entries(value)) {
22641
+ let name = prefix ? `${prefix}.${key}` : key;
22642
+ if (!entry || typeof entry !== "object") throw new Error(`Screen route ${name} must be a Remix route.`);
22643
+ if (!("pattern" in entry)) {
22644
+ visit(entry, name);
22645
+ continue;
22646
+ }
22647
+ if (!["GET", "ANY"].includes(entry.method) || typeof entry.href !== "function" || typeof entry.pattern?.source !== "string") throw new Error(`Screen route ${name} must be a GET route.`);
22648
+ let pattern = RoutePattern.parse(entry.pattern.source);
22649
+ let parts = pattern.toJSON();
22650
+ let pathname = `/${parts.pathname}`;
22651
+ if (parts.protocol || parts.hostname || parts.port || parts.pathname.split("/").some((segment) => /^\.{1,2}$/.test(decodeURIComponent(segment))) || pathname !== path && !pathname.startsWith(`${path}/`) && !pathname.startsWith(`${path}(/`)) throw new Error(`Screen route ${name} must stay under ${path}.`);
22652
+ result.push({
22653
+ name,
22654
+ pattern: pattern.source
22655
+ });
22656
+ }
22657
+ seen.delete(value);
22614
22658
  }
22615
- if (new TextEncoder().encode(JSON.stringify(patch)).byteLength > 122880) throw new BeadsValidationError("patch");
22616
- return patch;
22659
+ visit(routes, "");
22660
+ if (!result.length) throw new Error("Screen routes must contain at least one route.");
22661
+ return result;
22617
22662
  }
22663
+ //#endregion
22664
+ //#region ../project/src/lib/screens.ts
22618
22665
  /**
22619
- * Validates a new issue's fields.
22620
- * @param value - Untrusted data.
22621
- * @returns The fields, with a title.
22666
+ * Bud's browser of your live pages, with their comments.
22667
+ * @returns The built-in screen, ready to relabel by spreading.
22622
22668
  */
22623
- function parseBeadCreate(value) {
22624
- let patch = parseBeadPatch(value);
22625
- if (!patch.title) throw new BeadsValidationError("create.title");
22669
+ function pages() {
22626
22670
  return {
22627
- ...patch,
22628
- title: patch.title
22671
+ id: "pages",
22672
+ label: "Pages",
22673
+ icon: "browser",
22674
+ path: "/pages",
22675
+ module: "bud:pages"
22629
22676
  };
22630
22677
  }
22631
22678
  /**
22632
- * Validates a change to an issue.
22633
- * @param value - Untrusted data.
22634
- * @returns The action.
22679
+ * Bud's canvas of every route's screenshot.
22680
+ * @returns The built-in screen, ready to relabel by spreading.
22635
22681
  */
22636
- function parseBeadAction(value) {
22637
- let input = object$1(value, "action");
22638
- let expectedRevision = nonempty(input.expectedRevision, "action.expectedRevision", 256);
22639
- switch (input.kind) {
22640
- case "update":
22641
- strict$1(input, "action", [
22642
- "kind",
22643
- "expectedRevision",
22644
- "patch"
22645
- ]);
22646
- return {
22647
- kind: input.kind,
22648
- expectedRevision,
22649
- patch: parseBeadPatch(input.patch)
22650
- };
22651
- case "claim":
22652
- strict$1(input, "action", ["kind", "expectedRevision"]);
22653
- return {
22654
- kind: input.kind,
22655
- expectedRevision
22656
- };
22657
- case "close":
22658
- case "reopen":
22659
- strict$1(input, "action", [
22660
- "kind",
22661
- "expectedRevision",
22662
- "reason"
22663
- ]);
22664
- return {
22665
- kind: input.kind,
22666
- expectedRevision,
22667
- reason: text$4(input.reason ?? "", "action.reason", 4e3)
22668
- };
22669
- case "add-dependency":
22670
- strict$1(input, "action", [
22671
- "kind",
22672
- "expectedRevision",
22673
- "targetId",
22674
- "type"
22675
- ]);
22676
- if (!dependencyTypes.includes(input.type)) throw new BeadsValidationError("action.type");
22677
- return {
22678
- kind: input.kind,
22679
- expectedRevision,
22680
- targetId: identifier$1(input.targetId, "action.targetId"),
22681
- type: input.type
22682
- };
22683
- case "remove-dependency":
22684
- strict$1(input, "action", [
22685
- "kind",
22686
- "expectedRevision",
22687
- "targetId"
22688
- ]);
22689
- return {
22690
- kind: input.kind,
22691
- expectedRevision,
22692
- targetId: identifier$1(input.targetId, "action.targetId")
22693
- };
22694
- case "comment": {
22695
- strict$1(input, "action", [
22696
- "kind",
22697
- "expectedRevision",
22698
- "body"
22699
- ]);
22700
- let body = nonempty(input.body, "action.body", 6e4);
22701
- if (new TextEncoder().encode(body).byteLength > 122880) throw new BeadsValidationError("action.body");
22702
- return {
22703
- kind: input.kind,
22704
- expectedRevision,
22705
- body
22706
- };
22707
- }
22708
- default: throw new BeadsValidationError("action.kind");
22709
- }
22682
+ function canvas() {
22683
+ return {
22684
+ id: "canvas",
22685
+ label: "Canvas",
22686
+ icon: "grid",
22687
+ path: "/canvas",
22688
+ module: "bud:canvas"
22689
+ };
22710
22690
  }
22711
22691
  /**
22712
- * Validates a sync request.
22713
- * @param value - Untrusted data.
22714
- * @returns The request.
22692
+ * Bud's Beads task board.
22693
+ * @returns The built-in screen, ready to relabel by spreading.
22715
22694
  */
22716
- function parseBeadSync(value) {
22717
- let input = strict$1(value, "sync", ["direction"]);
22718
- if (input.direction !== "pull" && input.direction !== "push") throw new BeadsValidationError("sync.direction");
22719
- return { direction: input.direction };
22695
+ function tasks() {
22696
+ return {
22697
+ id: "tasks",
22698
+ label: "Tasks",
22699
+ icon: "tasks",
22700
+ path: "/tasks",
22701
+ module: "bud:tasks"
22702
+ };
22720
22703
  }
22721
- //#endregion
22722
- //#region ../beads/src/lib/protocol.ts
22723
22704
  /**
22724
- * Turns a CLI or export issue record into the browser's issue. CLI and export records share this
22725
- * one boundary before reaching the browser DTOs.
22726
- * @param value - The record.
22727
- * @param contentLoaded - Whether it holds the issue's long content.
22728
- * @returns The issue.
22705
+ * Bud's file browser and review.
22706
+ * @returns The built-in screen, ready to relabel by spreading.
22729
22707
  */
22730
- function parseCliIssue(value, contentLoaded = false) {
22731
- let input = object$1(value, "issue");
22732
- let id = identifier$1(input.id, "issue.id");
22733
- function dependencies(field) {
22734
- return array$1(input[field] ?? [], `issue.${field}`, (entry, path) => {
22735
- let edge = object$1(entry, path);
22736
- if ("depends_on_id" in edge) {
22737
- if (identifier$1(edge.issue_id, `${path}.issue_id`) !== id) throw new Error("Dependency belongs to a different issue.");
22738
- return {
22739
- id: identifier$1(edge.depends_on_id, `${path}.depends_on_id`),
22740
- type: identifier$1(edge.type, `${path}.type`),
22741
- title: null,
22742
- status: null,
22743
- priority: null,
22744
- issueType: null
22745
- };
22746
- }
22747
- return {
22748
- id: edge.id,
22749
- type: edge.dependency_type,
22750
- title: edge.title ?? null,
22751
- status: edge.status ?? null,
22752
- priority: edge.priority ?? null,
22753
- issueType: edge.issue_type ?? null
22754
- };
22755
- });
22708
+ function files() {
22709
+ return {
22710
+ id: "files",
22711
+ label: "Files",
22712
+ icon: "files",
22713
+ path: "/files",
22714
+ module: "bud:files"
22715
+ };
22716
+ }
22717
+ /** The screens shown when the config doesn't list any. */
22718
+ var defaultScreens = [
22719
+ pages(),
22720
+ canvas(),
22721
+ tasks(),
22722
+ files()
22723
+ ];
22724
+ var builtIns$1 = new Map(defaultScreens.map((screen) => [screen.module, screen]));
22725
+ /** First path segments owned by Bud's routes, its development server and its public assets. */
22726
+ var reservedScreenPaths = [
22727
+ "/comments",
22728
+ ...defaultScreens.map((screen) => screen.path),
22729
+ "/api",
22730
+ "/health",
22731
+ "/__bud",
22732
+ "/assets",
22733
+ "/app",
22734
+ "/src",
22735
+ "/node_modules",
22736
+ "/agents",
22737
+ "/brand",
22738
+ "/fonts",
22739
+ "/frameworks",
22740
+ "/licenses"
22741
+ ];
22742
+ var ScreenSchema = check(described(any().refine((value) => !!value && typeof value === "object"), "Each screen must be an object."), ({ id, label, icon = "layers", module, path }) => {
22743
+ if (typeof id !== "string" || !/^[a-z][a-z0-9-]{0,31}$/.test(id)) return "Screen ids are 1-32 lowercase letters, digits and hyphens.";
22744
+ if (typeof label !== "string" || !label.trim()) return `Screen ${id} needs a label.`;
22745
+ if (typeof icon !== "string" || !icon) return `Screen ${id} needs an icon name.`;
22746
+ if (typeof module !== "string") return `Screen ${id} needs a module.`;
22747
+ let builtIn = builtIns$1.get(module);
22748
+ path ??= builtIn?.path ?? `/${id}`;
22749
+ if (builtIn) {
22750
+ if (id !== builtIn.id || path !== builtIn.path) return `${module} keeps id "${builtIn.id}" and path "${builtIn.path}".`;
22751
+ return;
22756
22752
  }
22757
- return parseBeadIssue({
22758
- id,
22759
- title: input.title,
22760
- status: input.status,
22761
- priority: input.priority,
22762
- type: input.issue_type,
22763
- description: input.description ?? "",
22764
- design: input.design ?? "",
22765
- acceptanceCriteria: input.acceptance_criteria ?? "",
22766
- notes: input.notes ?? "",
22767
- assignee: input.assignee ?? null,
22768
- owner: input.owner ?? null,
22769
- createdBy: input.created_by ?? null,
22770
- createdAt: input.created_at,
22771
- updatedAt: input.updated_at,
22772
- startedAt: input.started_at ?? null,
22773
- closedAt: input.closed_at ?? null,
22774
- closeReason: input.close_reason ?? null,
22775
- dueAt: input.due_at ?? null,
22776
- deferUntil: input.defer_until ?? null,
22777
- estimatedMinutes: input.estimated_minutes ?? null,
22778
- externalRef: input.external_ref ?? null,
22779
- specId: input.spec_id ?? null,
22780
- labels: input.labels ?? [],
22781
- parentId: input.parent ?? null,
22782
- dependencies: dependencies("dependencies"),
22783
- dependents: dependencies("dependents"),
22784
- dependencyCount: input.dependency_count ?? null,
22785
- dependentCount: input.dependent_count ?? null,
22786
- commentCount: input.comment_count ?? null,
22787
- epicTotalChildren: input.epic_total_children ?? null,
22788
- epicClosedChildren: input.epic_closed_children ?? null,
22789
- epicCloseable: input.epic_closeable ?? null,
22790
- revision: input.revision ?? null,
22791
- metadata: input.metadata ?? null,
22792
- contentLoaded
22753
+ if (!/^\.\/[^\0\\]+\.(?:ts|tsx|js|jsx)$/.test(module)) return `Screen ${id} must be a ./ module ending in .ts, .tsx, .js or .jsx.`;
22754
+ if (typeof path !== "string" || !/^\/[a-z0-9][a-z0-9-]*$/.test(path)) return `Screen ${id} needs one static path segment, such as /${id}.`;
22755
+ if (defaultScreens.some((screen) => screen.id === id)) return `Screen id "${id}" is reserved for a built-in screen.`;
22756
+ if (reservedScreenPaths.includes(path)) return `Bud reserves the path ${path}.`;
22757
+ });
22758
+ /** Bud's navigation in order; file and icon checks need the app and happen at startup. */
22759
+ var BudScreensSchema = defaulted(delegate(described(array(any()).refine((screens) => screens.length > 0), "screens must list at least one screen."), (screens) => {
22760
+ let ids = /* @__PURE__ */ new Set();
22761
+ let paths = /* @__PURE__ */ new Set();
22762
+ return screens.map((value) => {
22763
+ let input = parseConfig(ScreenSchema, value);
22764
+ let { id, label, icon = "layers", module } = input;
22765
+ let path = input.path ?? builtIns$1.get(module)?.path ?? `/${id}`;
22766
+ if (ids.has(id) || paths.has(path)) throw new Error("Screen ids and paths must be unique.");
22767
+ ids.add(id);
22768
+ paths.add(path);
22769
+ if (input.routes !== void 0) resolveScreenRoutes(input.routes, path);
22770
+ return {
22771
+ id,
22772
+ label,
22773
+ icon,
22774
+ path,
22775
+ module,
22776
+ ...input.routes === void 0 ? {} : { routes: input.routes }
22777
+ };
22793
22778
  });
22779
+ }), () => defaultScreens.map((screen) => ({ ...screen })));
22780
+ //#endregion
22781
+ //#region ../project/src/lib/config.ts
22782
+ var objectLike = (value) => !!value && typeof value === "object";
22783
+ function section$3(message, shape) {
22784
+ let schema = object(shape);
22785
+ return createSchema((value, context) => objectLike(value) ? schema["~run"](Array.isArray(value) ? { ...value } : value, context) : fail(message, context.path));
22794
22786
  }
22795
- /**
22796
- * Turns the CLI's issue comments into discussion entries with unique IDs.
22797
- * @param value - The CLI's comment list, or nothing.
22798
- * @returns The comments.
22799
- */
22800
- function parseCliDiscussion(value) {
22801
- return unique(array$1(value ?? [], "comments", (entry, field) => {
22802
- let item = object$1(entry, field);
22803
- return parseBeadDiscussion({
22787
+ var ConfigFields = check(section$3("Bud config must export an object.", {
22788
+ routes: described(array(any()).refine((routes) => routes.length > 0), "bud.config must define at least one route."),
22789
+ name: optional(described(string().refine((name) => !!name.trim()), "name must be a nonempty string.")),
22790
+ target: optional(section$3("target must be an object.", {
22791
+ origin: any(),
22792
+ port: any()
22793
+ })),
22794
+ comments: optional(section$3("comments must be an object.", { remote: any() })),
22795
+ port: any(),
22796
+ screens: any(),
22797
+ storage: any(),
22798
+ designs: any(),
22799
+ previews: any(),
22800
+ files: any(),
22801
+ logo: any(),
22802
+ theme: any(),
22803
+ locale: any(),
22804
+ picker: any(),
22805
+ branches: any(),
22806
+ flows: any()
22807
+ }), ({ target }) => {
22808
+ if (target?.port === 0) return "target.port must be a listening port, not zero.";
22809
+ if (target?.origin !== void 0 && (typeof target.origin !== "string" || !target.origin.trim())) return "target.origin must be an HTTP(S) origin.";
22810
+ });
22811
+ var RouteSchema = check(any().transform((input) => typeof input === "string" ? {
22812
+ path: input,
22813
+ title: input === "/" ? "Home" : input
22814
+ } : input), (route) => {
22815
+ if (!route || typeof route.path !== "string" || !route.path.startsWith("/") || route.path.startsWith("//")) return "Canvas routes must be local paths beginning with a single /.";
22816
+ if (new URL(route.path, "http://bud.invalid").origin !== "http://bud.invalid" || route.path.includes("\\")) return "Canvas routes must stay on the target origin.";
22817
+ let id = route.id ?? routePath(route.path);
22818
+ if (typeof id !== "string" || !id.trim()) return "Canvas route IDs and paths must be unique.";
22819
+ if (typeof route.title !== "string" || !route.title.trim()) return "Each route needs a title.";
22820
+ if (route.pattern !== void 0 && (typeof route.pattern !== "string" || !route.pattern.startsWith("/"))) return "A route pattern must be a path beginning with /.";
22821
+ if (route.group !== void 0 && (typeof route.group !== "string" || !route.group.trim() || route.group.length > 120)) return "A route group must be a short name.";
22822
+ if (route.ready !== void 0 && route.ready !== false && !parseSafe(PreviewReadySchema, route.ready).success) return "A route ready must be false, a CSS selector, { selector } or { expression }.";
22823
+ }).transform((route) => {
22824
+ let path = routePath(route.path);
22825
+ return {
22826
+ id: route.id ?? path,
22827
+ path,
22828
+ title: route.title,
22829
+ ...route.pattern === void 0 ? {} : { pattern: route.pattern },
22830
+ ...route.group === void 0 ? {} : { group: route.group.trim() },
22831
+ ...route.ready === void 0 ? {} : { ready: route.ready === false ? null : parseConfig(PreviewReadySchema, route.ready) }
22832
+ };
22833
+ });
22834
+ function routePath(path) {
22835
+ let url = new URL(path, "http://bud.invalid");
22836
+ let search = url.searchParams.toString();
22837
+ return `${url.pathname}${search ? `?${search}` : ""}${url.hash}`;
22838
+ }
22839
+ /** Canvas routes with unique IDs and normalized paths. */
22840
+ var BudRoutesSchema = check(list(RouteSchema, "bud.config must define at least one route.", 1), (routes) => new Set(routes.map((route) => route.id)).size !== routes.length || new Set(routes.map((route) => route.path)).size !== routes.length ? "Canvas route IDs and paths must be unique." : void 0);
22841
+ var port = (value) => value === void 0 || Number.isInteger(value) && value >= 0 && value <= 65535;
22842
+ var remote$1 = (value) => typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value);
22843
+ /** A project's config, validated and normalized before Bud starts any listeners. */
22844
+ var BudConfigSchema = delegate(check(delegate(check(check(delegate(check(delegate(ConfigFields, (config) => ({
22845
+ ...config,
22846
+ routes: parseConfig(BudRoutesSchema, config.routes)
22847
+ })), ({ port: companion, target }) => port(companion ?? 0) && port(target?.port) ? void 0 : "Ports must be integers between 0 and 65535."), ({ target, ...config }) => {
22848
+ let origin = target?.origin ?? (target?.port ? `http://127.0.0.1:${target.port}` : void 0);
22849
+ return {
22850
+ ...config,
22851
+ targetOrigin: origin ? normalizeOrigin(origin, "target.origin must be an HTTP(S) origin without a path or credentials.") : origin
22852
+ };
22853
+ }), ({ locale }) => locale === void 0 || locale === "en" || locale === "de" ? void 0 : "locale must be \"en\" or \"de\"."), ({ comments }) => remote$1(comments?.remote ?? "origin") ? void 0 : "comments.remote must be a Git remote name."), (config) => {
22854
+ let flows = parseConfig(BudFlowsSchema, config.flows);
22855
+ let steps = flows.flatMap((flow) => flow.steps.map((step) => flowStepRoute(flow, step)));
22856
+ let home = config.routes.findIndex((route) => route.path === "/") + 1;
22857
+ return {
22858
+ ...config,
22859
+ flows,
22860
+ routes: [
22861
+ ...config.routes.slice(0, home),
22862
+ ...steps,
22863
+ ...config.routes.slice(home)
22864
+ ]
22865
+ };
22866
+ }), ({ routes }) => new Set(routes.map((route) => route.id)).size !== routes.length || new Set(routes.map((route) => route.path)).size !== routes.length ? "A flow step must not reuse a route’s id or path." : void 0), (config) => ({
22867
+ name: config.name ?? "Your project",
22868
+ routes: config.routes,
22869
+ flows: config.flows,
22870
+ port: config.port ?? 0,
22871
+ targetOrigin: config.targetOrigin,
22872
+ remote: config.comments?.remote ?? "origin",
22873
+ screens: parseConfig(BudScreensSchema, config.screens),
22874
+ storage: parseConfig(BudStorageSchema, config.storage),
22875
+ designs: parseConfig(BudDesignsSchema, config.designs),
22876
+ previews: parseConfig(BudPreviewsSchema, config.previews),
22877
+ files: parseConfig(BudFilesSchema, config.files),
22878
+ logo: parseConfig(BudLogoSchema, config.logo),
22879
+ theme: parseConfig(BudThemeSchema, config.theme),
22880
+ locale: config.locale === void 0 ? "en" : config.locale,
22881
+ picker: parseConfig(BudPickerSchema, config.picker),
22882
+ branches: parseConfig(BudBranchesSchema, config.branches)
22883
+ }));
22884
+ /**
22885
+ * Validates executable project config before starting any listeners.
22886
+ * @param value - The config module's default export.
22887
+ * @returns Normalized route IDs, ports, and URLs.
22888
+ */
22889
+ function resolveBudConfig(value) {
22890
+ return parseConfig(BudConfigSchema, value);
22891
+ }
22892
+ /**
22893
+ * Returns a bare HTTP(S) origin, rejecting credentials, paths, queries and fragments.
22894
+ * @param origin - The URL to check.
22895
+ * @param message - The error's message when it is not a bare origin.
22896
+ * @returns The URL's origin.
22897
+ */
22898
+ function normalizeOrigin(origin, message) {
22899
+ let url = new URL(origin);
22900
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.pathname !== "/" || url.search || url.hash) throw new Error(message);
22901
+ return url.origin;
22902
+ }
22903
+ //#endregion
22904
+ //#region ../beads/src/lib/contracts.ts
22905
+ var MAX_BEADS = 1e3;
22906
+ //#endregion
22907
+ //#region ../beads/src/lib/validation.ts
22908
+ /** Beads returned or was sent data of the wrong shape; `field` names where. */
22909
+ var BeadsValidationError = class extends Error {
22910
+ field;
22911
+ constructor(field) {
22912
+ super(`Invalid Beads data at ${field}.`);
22913
+ this.name = "BeadsValidationError";
22914
+ this.field = field;
22915
+ }
22916
+ };
22917
+ /**
22918
+ * Checks that a value is a JSON object.
22919
+ * @param value - Untrusted data.
22920
+ * @param field - Where the value sits, for the error.
22921
+ * @returns The object.
22922
+ */
22923
+ function object$1(value, field) {
22924
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new BeadsValidationError(field);
22925
+ return value;
22926
+ }
22927
+ /**
22928
+ * Checks a string without NUL characters within a length limit.
22929
+ * @param value - Untrusted data.
22930
+ * @param field - Where the value sits, for the error.
22931
+ * @param max - The most characters it may have.
22932
+ * @returns The string.
22933
+ */
22934
+ function text$3(value, field, max = 262144) {
22935
+ if (typeof value !== "string" || value.length > max || value.includes("\0")) throw new BeadsValidationError(field);
22936
+ return value;
22937
+ }
22938
+ /**
22939
+ * Checks an ID, name or commit made of letters, digits, dots, underscores and hyphens.
22940
+ * @param value - Untrusted data.
22941
+ * @param field - Where the value sits, for the error.
22942
+ * @returns The identifier.
22943
+ */
22944
+ function identifier$1(value, field) {
22945
+ let result = text$3(value, field, 512);
22946
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(result)) throw new BeadsValidationError(field);
22947
+ return result;
22948
+ }
22949
+ /**
22950
+ * Checks an optional string.
22951
+ * @param value - Untrusted data.
22952
+ * @param field - Where the value sits, for the error.
22953
+ * @param max - The most characters it may have.
22954
+ * @returns The string, or null when it is missing.
22955
+ */
22956
+ function nullableText(value, field, max) {
22957
+ return value == null ? null : text$3(value, field, max);
22958
+ }
22959
+ /**
22960
+ * Checks a nonnegative safe integer.
22961
+ * @param value - Untrusted data.
22962
+ * @param field - Where the value sits, for the error.
22963
+ * @param max - The largest value it may have.
22964
+ * @returns The integer.
22965
+ */
22966
+ function integer(value, field, max = Number.MAX_SAFE_INTEGER) {
22967
+ if (!Number.isSafeInteger(value) || value < 0 || value > max) throw new BeadsValidationError(field);
22968
+ return value;
22969
+ }
22970
+ /**
22971
+ * Checks an optional nonnegative integer.
22972
+ * @param value - Untrusted data.
22973
+ * @param field - Where the value sits, for the error.
22974
+ * @param max - The largest value it may have.
22975
+ * @returns The integer, or null when it is missing.
22976
+ */
22977
+ function nullableInteger(value, field, max) {
22978
+ return value == null ? null : integer(value, field, max);
22979
+ }
22980
+ /**
22981
+ * Checks a boolean.
22982
+ * @param value - Untrusted data.
22983
+ * @param field - Where the value sits, for the error.
22984
+ * @returns The boolean.
22985
+ */
22986
+ function boolean$2(value, field) {
22987
+ if (typeof value !== "boolean") throw new BeadsValidationError(field);
22988
+ return value;
22989
+ }
22990
+ /**
22991
+ * Checks an ISO 8601 timestamp with a time zone.
22992
+ * @param value - Untrusted data.
22993
+ * @param field - Where the value sits, for the error.
22994
+ * @returns The timestamp as given.
22995
+ */
22996
+ function timestamp(value, field) {
22997
+ let result = text$3(value, field, 64);
22998
+ if (!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(result) || !Number.isFinite(Date.parse(result))) throw new BeadsValidationError(field);
22999
+ return result;
23000
+ }
23001
+ /**
23002
+ * Checks an optional ISO 8601 timestamp.
23003
+ * @param value - Untrusted data.
23004
+ * @param field - Where the value sits, for the error.
23005
+ * @returns The timestamp, or null when it is missing.
23006
+ */
23007
+ function nullableTimestamp(value, field) {
23008
+ return value == null ? null : timestamp(value, field);
23009
+ }
23010
+ /**
23011
+ * Checks a bounded array and each of its entries.
23012
+ * @param value - Untrusted data.
23013
+ * @param field - Where the value sits, for the error.
23014
+ * @param parse - Checks one entry.
23015
+ * @param max - The most entries it may have.
23016
+ * @returns The parsed entries.
23017
+ */
23018
+ function array$1(value, field, parse, max = 1e4) {
23019
+ if (!Array.isArray(value) || value.length > max) throw new BeadsValidationError(field);
23020
+ return value.map((entry, i) => parse(entry, `${field}[${i}]`));
23021
+ }
23022
+ /**
23023
+ * Checks that no two values share a key.
23024
+ * @param values - The values to check.
23025
+ * @param key - Gives each value's key.
23026
+ * @param field - Where the values sit, for the error.
23027
+ * @returns The values.
23028
+ */
23029
+ function unique(values, key, field) {
23030
+ if (new Set(values.map(key)).size !== values.length) throw new BeadsValidationError(field);
23031
+ return values;
23032
+ }
23033
+ /**
23034
+ * Checks JSON metadata, bounding its depth and its total number of values.
23035
+ * @param value - Untrusted data.
23036
+ * @param field - Where the value sits, for the error.
23037
+ * @param depth - How deep this value is nested.
23038
+ * @param budget - The values left to check, shared across the whole tree.
23039
+ * @returns The metadata.
23040
+ */
23041
+ function metadata(value, field, depth = 0, budget = { remaining: 1e4 }) {
23042
+ if (depth > 12 || --budget.remaining < 0) throw new BeadsValidationError(field);
23043
+ if (value === null || typeof value === "boolean") return value;
23044
+ if (typeof value === "number" && Number.isFinite(value)) return value;
23045
+ if (typeof value === "string") return text$3(value, field);
23046
+ if (Array.isArray(value)) return value.map((entry, index) => metadata(entry, `${field}[${index}]`, depth + 1, budget));
23047
+ let input = object$1(value, field);
23048
+ return Object.fromEntries(Object.entries(input).map(([key, entry]) => [text$3(key, field, 512), metadata(entry, `${field}.${key}`, depth + 1, budget)]));
23049
+ }
23050
+ function dependency(value, field) {
23051
+ let input = object$1(value, field);
23052
+ return {
23053
+ id: identifier$1(input.id, `${field}.id`),
23054
+ type: identifier$1(input.type, `${field}.type`),
23055
+ title: nullableText(input.title, `${field}.title`),
23056
+ status: nullableText(input.status, `${field}.status`, 128),
23057
+ priority: nullableInteger(input.priority, `${field}.priority`, 4),
23058
+ issueType: nullableText(input.issueType, `${field}.issueType`, 128)
23059
+ };
23060
+ }
23061
+ /**
23062
+ * Validates an issue.
23063
+ * @param value - Untrusted data.
23064
+ * @param field - Where the value sits, for the error.
23065
+ * @returns The issue.
23066
+ */
23067
+ function parseBeadIssue(value, field = "issue") {
23068
+ let input = object$1(value, field);
23069
+ let string = (key, max) => text$3(input[key], `${field}.${key}`, max);
23070
+ let optional = (key, max) => nullableText(input[key], `${field}.${key}`, max);
23071
+ let count = (key) => nullableInteger(input[key], `${field}.${key}`);
23072
+ let date = (key) => nullableTimestamp(input[key], `${field}.${key}`);
23073
+ return {
23074
+ id: identifier$1(input.id, `${field}.id`),
23075
+ title: string("title"),
23076
+ status: identifier$1(input.status, `${field}.status`),
23077
+ priority: integer(input.priority, `${field}.priority`, 4),
23078
+ type: identifier$1(input.type, `${field}.type`),
23079
+ description: string("description"),
23080
+ design: string("design"),
23081
+ acceptanceCriteria: string("acceptanceCriteria"),
23082
+ notes: string("notes"),
23083
+ assignee: optional("assignee"),
23084
+ owner: optional("owner"),
23085
+ createdBy: optional("createdBy"),
23086
+ createdAt: timestamp(input.createdAt, `${field}.createdAt`),
23087
+ updatedAt: timestamp(input.updatedAt, `${field}.updatedAt`),
23088
+ startedAt: date("startedAt"),
23089
+ closedAt: date("closedAt"),
23090
+ closeReason: optional("closeReason"),
23091
+ dueAt: date("dueAt"),
23092
+ deferUntil: date("deferUntil"),
23093
+ estimatedMinutes: count("estimatedMinutes"),
23094
+ externalRef: optional("externalRef"),
23095
+ specId: optional("specId"),
23096
+ labels: unique(array$1(input.labels, `${field}.labels`, (item, path) => text$3(item, path, 512), 1e3), (item) => item, `${field}.labels`),
23097
+ parentId: input.parentId == null ? null : identifier$1(input.parentId, `${field}.parentId`),
23098
+ dependencies: array$1(input.dependencies, `${field}.dependencies`, dependency),
23099
+ dependents: array$1(input.dependents, `${field}.dependents`, dependency),
23100
+ dependencyCount: count("dependencyCount"),
23101
+ dependentCount: count("dependentCount"),
23102
+ commentCount: count("commentCount"),
23103
+ epicTotalChildren: count("epicTotalChildren"),
23104
+ epicClosedChildren: count("epicClosedChildren"),
23105
+ epicCloseable: input.epicCloseable == null ? null : boolean$2(input.epicCloseable, `${field}.epicCloseable`),
23106
+ revision: optional("revision", 256),
23107
+ metadata: metadata(input.metadata, `${field}.metadata`),
23108
+ contentLoaded: boolean$2(input.contentLoaded, `${field}.contentLoaded`)
23109
+ };
23110
+ }
23111
+ /**
23112
+ * Validates a connection state.
23113
+ * @param value - Untrusted data.
23114
+ * @returns The connection.
23115
+ */
23116
+ function parseBeadsConnection(value) {
23117
+ let input = object$1(value, "connection");
23118
+ if (input.state === "ready") {
23119
+ let project = object$1(input.project, "connection.project");
23120
+ if (input.schemaVersion !== 1) throw new BeadsValidationError("connection.schemaVersion");
23121
+ return {
23122
+ state: "ready",
23123
+ version: text$3(input.version, "connection.version", 128),
23124
+ schemaVersion: 1,
23125
+ project: {
23126
+ name: text$3(project.name, "project.name", 512),
23127
+ prefix: nullableText(project.prefix, "project.prefix", 512),
23128
+ backend: text$3(project.backend, "project.backend", 128),
23129
+ mode: nullableText(project.mode, "project.mode", 128),
23130
+ redirected: boolean$2(project.redirected, "project.redirected"),
23131
+ worktree: boolean$2(project.worktree, "project.worktree")
23132
+ }
23133
+ };
23134
+ }
23135
+ if (![
23136
+ "unavailable",
23137
+ "not-initialized",
23138
+ "unsupported-version"
23139
+ ].includes(input.state)) throw new BeadsValidationError("connection.state");
23140
+ return {
23141
+ state: input.state,
23142
+ version: nullableText(input.version, "connection.version", 128),
23143
+ message: text$3(input.message, "connection.message", 1e3)
23144
+ };
23145
+ }
23146
+ /**
23147
+ * Validates a status definition.
23148
+ * @param value - Untrusted data.
23149
+ * @param field - Where the value sits, for the error.
23150
+ * @returns The status.
23151
+ */
23152
+ function parseBeadStatus(value, field = "status") {
23153
+ let input = object$1(value, field);
23154
+ return {
23155
+ name: identifier$1(input.name, `${field}.name`),
23156
+ category: nullableText(input.category, `${field}.category`, 128),
23157
+ description: text$3(input.description, `${field}.description`),
23158
+ custom: boolean$2(input.custom, `${field}.custom`)
23159
+ };
23160
+ }
23161
+ /**
23162
+ * Validates an issue type definition.
23163
+ * @param value - Untrusted data.
23164
+ * @param field - Where the value sits, for the error.
23165
+ * @returns The type.
23166
+ */
23167
+ function parseBeadType(value, field = "type") {
23168
+ let input = object$1(value, field);
23169
+ if (![
23170
+ "core",
23171
+ "system",
23172
+ "custom"
23173
+ ].includes(input.kind)) throw new BeadsValidationError(`${field}.kind`);
23174
+ return {
23175
+ name: identifier$1(input.name, `${field}.name`),
23176
+ description: text$3(input.description, `${field}.description`),
23177
+ kind: input.kind
23178
+ };
23179
+ }
23180
+ /**
23181
+ * Validates a board snapshot, which holds no rows unless its connection is ready.
23182
+ * @param value - Untrusted data.
23183
+ * @returns The snapshot.
23184
+ */
23185
+ function parseBeadsSnapshot(value) {
23186
+ let input = object$1(value, "snapshot");
23187
+ let connection = parseBeadsConnection(input.connection);
23188
+ let issues = unique(array$1(input.issues, "snapshot.issues", parseBeadIssue, MAX_BEADS), (issue) => issue.id, "snapshot.issues");
23189
+ let readyIds = unique(array$1(input.readyIds, "snapshot.readyIds", identifier$1, MAX_BEADS), (id) => id, "snapshot.readyIds");
23190
+ let statuses = unique(array$1(input.statuses, "snapshot.statuses", parseBeadStatus, 1e3), (item) => item.name, "snapshot.statuses");
23191
+ let types = unique(array$1(input.types, "snapshot.types", parseBeadType, 1e3), (item) => item.name, "snapshot.types");
23192
+ let truncated = boolean$2(input.truncated, "snapshot.truncated");
23193
+ if (connection.state !== "ready" && (issues.length || readyIds.length || statuses.length || types.length || truncated)) throw new BeadsValidationError("snapshot.connection");
23194
+ return {
23195
+ connection,
23196
+ issues,
23197
+ readyIds,
23198
+ statuses,
23199
+ types,
23200
+ truncated
23201
+ };
23202
+ }
23203
+ /**
23204
+ * Validates an issue comment.
23205
+ * @param value - Untrusted data.
23206
+ * @param field - Where the value sits, for the error.
23207
+ * @returns The comment.
23208
+ */
23209
+ function parseBeadDiscussion(value, field = "discussion") {
23210
+ let input = object$1(value, field);
23211
+ return {
23212
+ id: identifier$1(input.id, `${field}.id`),
23213
+ issueId: identifier$1(input.issueId, `${field}.issueId`),
23214
+ author: text$3(input.author, `${field}.author`),
23215
+ body: text$3(input.body, `${field}.body`),
23216
+ createdAt: timestamp(input.createdAt, `${field}.createdAt`)
23217
+ };
23218
+ }
23219
+ /**
23220
+ * Validates an issue's version at one commit.
23221
+ * @param value - Untrusted data.
23222
+ * @param field - Where the value sits, for the error.
23223
+ * @returns The version.
23224
+ */
23225
+ function parseBeadHistory(value, field = "history") {
23226
+ let input = object$1(value, field);
23227
+ return {
23228
+ commit: identifier$1(input.commit, `${field}.commit`),
23229
+ author: text$3(input.author, `${field}.author`),
23230
+ createdAt: timestamp(input.createdAt, `${field}.createdAt`),
23231
+ issue: input.issue == null ? null : parseBeadIssue(input.issue, `${field}.issue`)
23232
+ };
23233
+ }
23234
+ /**
23235
+ * Validates an event log entry.
23236
+ * @param value - Untrusted data.
23237
+ * @param field - Where the value sits, for the error.
23238
+ * @returns The event.
23239
+ */
23240
+ function parseBeadEvent(value, field = "event") {
23241
+ let input = object$1(value, field);
23242
+ return {
23243
+ id: identifier$1(input.id, `${field}.id`),
23244
+ issueId: identifier$1(input.issueId, `${field}.issueId`),
23245
+ type: identifier$1(input.type, `${field}.type`),
23246
+ actor: text$3(input.actor, `${field}.actor`),
23247
+ oldValue: nullableText(input.oldValue, `${field}.oldValue`),
23248
+ newValue: nullableText(input.newValue, `${field}.newValue`),
23249
+ comment: nullableText(input.comment, `${field}.comment`),
23250
+ createdAt: timestamp(input.createdAt, `${field}.createdAt`)
23251
+ };
23252
+ }
23253
+ function section$2(value, field, parser, max) {
23254
+ let input = object$1(value, field);
23255
+ let items = array$1(input.items, `${field}.items`, parser, max);
23256
+ let truncated = boolean$2(input.truncated, `${field}.truncated`);
23257
+ if (input.state === "available") return {
23258
+ state: "available",
23259
+ items,
23260
+ truncated
23261
+ };
23262
+ if (input.state === "unsupported" && items.length === 0 && truncated === false) return {
23263
+ state: "unsupported",
23264
+ items: [],
23265
+ truncated: false
23266
+ };
23267
+ throw new BeadsValidationError(`${field}.state`);
23268
+ }
23269
+ /**
23270
+ * Validates an issue's detail, whose sections must all belong to that issue.
23271
+ * @param value - Untrusted data.
23272
+ * @returns The detail.
23273
+ */
23274
+ function parseBeadDetail(value) {
23275
+ let input = object$1(value, "detail");
23276
+ let connection = parseBeadsConnection(input.connection);
23277
+ if (connection.state !== "ready") throw new BeadsValidationError("detail.connection");
23278
+ let issue = parseBeadIssue(input.issue);
23279
+ let discussion = section$2(input.discussion, "discussion", parseBeadDiscussion, 200);
23280
+ let history = section$2(input.history, "history", parseBeadHistory, 50);
23281
+ let events = section$2(input.events, "events", parseBeadEvent, 50);
23282
+ if (!issue.contentLoaded || discussion.items.some((entry) => entry.issueId !== issue.id) || events.items.some((entry) => entry.issueId !== issue.id) || history.items.some((entry) => entry.issue && entry.issue.id !== issue.id)) throw new BeadsValidationError("detail.issue");
23283
+ return {
23284
+ connection,
23285
+ issue,
23286
+ discussion,
23287
+ history,
23288
+ events
23289
+ };
23290
+ }
23291
+ //#endregion
23292
+ //#region ../beads/src/lib/actions.ts
23293
+ var dependencyTypes = [
23294
+ "blocks",
23295
+ "tracks",
23296
+ "related",
23297
+ "discovered-from",
23298
+ "until",
23299
+ "caused-by",
23300
+ "validates",
23301
+ "relates-to",
23302
+ "supersedes"
23303
+ ];
23304
+ var fields$2 = [
23305
+ "title",
23306
+ "type",
23307
+ "status",
23308
+ "priority",
23309
+ "assignee",
23310
+ "labels",
23311
+ "parentId",
23312
+ "description",
23313
+ "design",
23314
+ "acceptanceCriteria",
23315
+ "notes",
23316
+ "estimatedMinutes",
23317
+ "externalRef",
23318
+ "specId",
23319
+ "dueAt",
23320
+ "deferUntil",
23321
+ "metadata"
23322
+ ];
23323
+ function strict$1(value, name, allowed) {
23324
+ let input = object$1(value, name);
23325
+ for (let key of Object.keys(input)) if (!allowed.includes(key)) throw new BeadsValidationError(`${name}.${key}`);
23326
+ return input;
23327
+ }
23328
+ function nonempty(value, field, max) {
23329
+ let result = text$3(value, field, max).trim();
23330
+ if (!result) throw new BeadsValidationError(field);
23331
+ return result;
23332
+ }
23333
+ /**
23334
+ * Validates an update's fields, rejecting unknown and oversized ones.
23335
+ * @param value - Untrusted data.
23336
+ * @returns The patch.
23337
+ */
23338
+ function parseBeadPatch(value) {
23339
+ let input = strict$1(value, "patch", fields$2);
23340
+ let patch = {};
23341
+ if (!Object.keys(input).length) throw new BeadsValidationError("patch");
23342
+ if ("title" in input) patch.title = nonempty(input.title, "patch.title", 1e3);
23343
+ if ("type" in input) patch.type = identifier$1(input.type, "patch.type");
23344
+ if ("status" in input) patch.status = identifier$1(input.status, "patch.status");
23345
+ if ("priority" in input) patch.priority = integer(input.priority, "patch.priority", 4);
23346
+ if ("assignee" in input) patch.assignee = text$3(input.assignee, "patch.assignee", 256).trim();
23347
+ if ("labels" in input) patch.labels = unique(array$1(input.labels, "patch.labels", (value, field) => {
23348
+ let label = nonempty(value, field, 128);
23349
+ if (/[\r\n,]/.test(label)) throw new BeadsValidationError(field);
23350
+ return label;
23351
+ }, 100), (label) => label, "patch.labels");
23352
+ if ("parentId" in input) patch.parentId = input.parentId === null ? null : identifier$1(input.parentId, "patch.parentId");
23353
+ for (let key of [
23354
+ "description",
23355
+ "design",
23356
+ "acceptanceCriteria",
23357
+ "notes"
23358
+ ]) if (key in input) patch[key] = text$3(input[key], `patch.${key}`, 6e4);
23359
+ if ("estimatedMinutes" in input) patch.estimatedMinutes = integer(input.estimatedMinutes, "patch.estimatedMinutes", 1e6);
23360
+ for (let key of ["externalRef", "specId"]) if (key in input) patch[key] = text$3(input[key], `patch.${key}`, 2048);
23361
+ for (let key of ["dueAt", "deferUntil"]) if (key in input) patch[key] = text$3(input[key], `patch.${key}`, 128);
23362
+ if ("metadata" in input) {
23363
+ let source = object$1(input.metadata, "patch.metadata");
23364
+ if (JSON.stringify(source).length > 32e3) throw new BeadsValidationError("patch.metadata");
23365
+ patch.metadata = metadata(source, "patch.metadata");
23366
+ }
23367
+ if (new TextEncoder().encode(JSON.stringify(patch)).byteLength > 122880) throw new BeadsValidationError("patch");
23368
+ return patch;
23369
+ }
23370
+ /**
23371
+ * Validates a new issue's fields.
23372
+ * @param value - Untrusted data.
23373
+ * @returns The fields, with a title.
23374
+ */
23375
+ function parseBeadCreate(value) {
23376
+ let patch = parseBeadPatch(value);
23377
+ if (!patch.title) throw new BeadsValidationError("create.title");
23378
+ return {
23379
+ ...patch,
23380
+ title: patch.title
23381
+ };
23382
+ }
23383
+ /**
23384
+ * Validates a change to an issue.
23385
+ * @param value - Untrusted data.
23386
+ * @returns The action.
23387
+ */
23388
+ function parseBeadAction(value) {
23389
+ let input = object$1(value, "action");
23390
+ let expectedRevision = nonempty(input.expectedRevision, "action.expectedRevision", 256);
23391
+ switch (input.kind) {
23392
+ case "update":
23393
+ strict$1(input, "action", [
23394
+ "kind",
23395
+ "expectedRevision",
23396
+ "patch"
23397
+ ]);
23398
+ return {
23399
+ kind: input.kind,
23400
+ expectedRevision,
23401
+ patch: parseBeadPatch(input.patch)
23402
+ };
23403
+ case "claim":
23404
+ strict$1(input, "action", ["kind", "expectedRevision"]);
23405
+ return {
23406
+ kind: input.kind,
23407
+ expectedRevision
23408
+ };
23409
+ case "close":
23410
+ case "reopen":
23411
+ strict$1(input, "action", [
23412
+ "kind",
23413
+ "expectedRevision",
23414
+ "reason"
23415
+ ]);
23416
+ return {
23417
+ kind: input.kind,
23418
+ expectedRevision,
23419
+ reason: text$3(input.reason ?? "", "action.reason", 4e3)
23420
+ };
23421
+ case "add-dependency":
23422
+ strict$1(input, "action", [
23423
+ "kind",
23424
+ "expectedRevision",
23425
+ "targetId",
23426
+ "type"
23427
+ ]);
23428
+ if (!dependencyTypes.includes(input.type)) throw new BeadsValidationError("action.type");
23429
+ return {
23430
+ kind: input.kind,
23431
+ expectedRevision,
23432
+ targetId: identifier$1(input.targetId, "action.targetId"),
23433
+ type: input.type
23434
+ };
23435
+ case "remove-dependency":
23436
+ strict$1(input, "action", [
23437
+ "kind",
23438
+ "expectedRevision",
23439
+ "targetId"
23440
+ ]);
23441
+ return {
23442
+ kind: input.kind,
23443
+ expectedRevision,
23444
+ targetId: identifier$1(input.targetId, "action.targetId")
23445
+ };
23446
+ case "comment": {
23447
+ strict$1(input, "action", [
23448
+ "kind",
23449
+ "expectedRevision",
23450
+ "body"
23451
+ ]);
23452
+ let body = nonempty(input.body, "action.body", 6e4);
23453
+ if (new TextEncoder().encode(body).byteLength > 122880) throw new BeadsValidationError("action.body");
23454
+ return {
23455
+ kind: input.kind,
23456
+ expectedRevision,
23457
+ body
23458
+ };
23459
+ }
23460
+ default: throw new BeadsValidationError("action.kind");
23461
+ }
23462
+ }
23463
+ /**
23464
+ * Validates a sync request.
23465
+ * @param value - Untrusted data.
23466
+ * @returns The request.
23467
+ */
23468
+ function parseBeadSync(value) {
23469
+ let input = strict$1(value, "sync", ["direction"]);
23470
+ if (input.direction !== "pull" && input.direction !== "push") throw new BeadsValidationError("sync.direction");
23471
+ return { direction: input.direction };
23472
+ }
23473
+ //#endregion
23474
+ //#region ../beads/src/lib/protocol.ts
23475
+ /**
23476
+ * Turns a CLI or export issue record into the browser's issue. CLI and export records share this
23477
+ * one boundary before reaching the browser DTOs.
23478
+ * @param value - The record.
23479
+ * @param contentLoaded - Whether it holds the issue's long content.
23480
+ * @returns The issue.
23481
+ */
23482
+ function parseCliIssue(value, contentLoaded = false) {
23483
+ let input = object$1(value, "issue");
23484
+ let id = identifier$1(input.id, "issue.id");
23485
+ function dependencies(field) {
23486
+ return array$1(input[field] ?? [], `issue.${field}`, (entry, path) => {
23487
+ let edge = object$1(entry, path);
23488
+ if ("depends_on_id" in edge) {
23489
+ if (identifier$1(edge.issue_id, `${path}.issue_id`) !== id) throw new Error("Dependency belongs to a different issue.");
23490
+ return {
23491
+ id: identifier$1(edge.depends_on_id, `${path}.depends_on_id`),
23492
+ type: identifier$1(edge.type, `${path}.type`),
23493
+ title: null,
23494
+ status: null,
23495
+ priority: null,
23496
+ issueType: null
23497
+ };
23498
+ }
23499
+ return {
23500
+ id: edge.id,
23501
+ type: edge.dependency_type,
23502
+ title: edge.title ?? null,
23503
+ status: edge.status ?? null,
23504
+ priority: edge.priority ?? null,
23505
+ issueType: edge.issue_type ?? null
23506
+ };
23507
+ });
23508
+ }
23509
+ return parseBeadIssue({
23510
+ id,
23511
+ title: input.title,
23512
+ status: input.status,
23513
+ priority: input.priority,
23514
+ type: input.issue_type,
23515
+ description: input.description ?? "",
23516
+ design: input.design ?? "",
23517
+ acceptanceCriteria: input.acceptance_criteria ?? "",
23518
+ notes: input.notes ?? "",
23519
+ assignee: input.assignee ?? null,
23520
+ owner: input.owner ?? null,
23521
+ createdBy: input.created_by ?? null,
23522
+ createdAt: input.created_at,
23523
+ updatedAt: input.updated_at,
23524
+ startedAt: input.started_at ?? null,
23525
+ closedAt: input.closed_at ?? null,
23526
+ closeReason: input.close_reason ?? null,
23527
+ dueAt: input.due_at ?? null,
23528
+ deferUntil: input.defer_until ?? null,
23529
+ estimatedMinutes: input.estimated_minutes ?? null,
23530
+ externalRef: input.external_ref ?? null,
23531
+ specId: input.spec_id ?? null,
23532
+ labels: input.labels ?? [],
23533
+ parentId: input.parent ?? null,
23534
+ dependencies: dependencies("dependencies"),
23535
+ dependents: dependencies("dependents"),
23536
+ dependencyCount: input.dependency_count ?? null,
23537
+ dependentCount: input.dependent_count ?? null,
23538
+ commentCount: input.comment_count ?? null,
23539
+ epicTotalChildren: input.epic_total_children ?? null,
23540
+ epicClosedChildren: input.epic_closed_children ?? null,
23541
+ epicCloseable: input.epic_closeable ?? null,
23542
+ revision: input.revision ?? null,
23543
+ metadata: input.metadata ?? null,
23544
+ contentLoaded
23545
+ });
23546
+ }
23547
+ /**
23548
+ * Turns the CLI's issue comments into discussion entries with unique IDs.
23549
+ * @param value - The CLI's comment list, or nothing.
23550
+ * @returns The comments.
23551
+ */
23552
+ function parseCliDiscussion(value) {
23553
+ return unique(array$1(value ?? [], "comments", (entry, field) => {
23554
+ let item = object$1(entry, field);
23555
+ return parseBeadDiscussion({
22804
23556
  id: item.id,
22805
23557
  issueId: item.issue_id,
22806
23558
  author: item.author,
@@ -23280,7 +24032,7 @@ function payload$1(value) {
23280
24032
  return /^[{[]/.test(value.trim()) ? null : value;
23281
24033
  }
23282
24034
  }
23283
- function text$3(value) {
24035
+ function text$2(value) {
23284
24036
  return typeof value === "string" || typeof value === "number" ? String(value).replace(/\s+/g, " ").trim() : "";
23285
24037
  }
23286
24038
  function field(value, key) {
@@ -23289,7 +24041,7 @@ function field(value, key) {
23289
24041
  function changed$1(key, before, after) {
23290
24042
  let name = fields[key] ?? label$1(key).toLowerCase();
23291
24043
  if (key === "labels") {
23292
- let labels = (value) => Array.isArray(value) ? value.map(text$3).filter(Boolean) : text$3(value).split(",").map((v) => v.trim()).filter(Boolean);
24044
+ let labels = (value) => Array.isArray(value) ? value.map(text$2).filter(Boolean) : text$2(value).split(",").map((v) => v.trim()).filter(Boolean);
23293
24045
  let oldLabels = labels(before);
23294
24046
  let newLabels = labels(after);
23295
24047
  let added = newLabels.filter((value) => !oldLabels.includes(value));
@@ -23305,7 +24057,7 @@ function changed$1(key, before, after) {
23305
24057
  "metadata"
23306
24058
  ].includes(key)) return `updated ${name}`;
23307
24059
  let display = (value) => {
23308
- let result = text$3(value);
24060
+ let result = text$2(value);
23309
24061
  if (!result) return key === "assignee" ? "Unassigned" : "None";
23310
24062
  if (key === "priority" && /^\d$/.test(result)) return `P${result}`;
23311
24063
  return key === "status" || key === "issue_type" ? label$1(result) : result;
@@ -23326,7 +24078,7 @@ function beadActivitySummary(event) {
23326
24078
  case "created": return "created this task";
23327
24079
  case "closed":
23328
24080
  case "reopened": {
23329
- let reason = text$3(payload$1(event.comment)) || text$3(field(after, "close_reason")) || text$3(field(after, "reason"));
24081
+ let reason = text$2(payload$1(event.comment)) || text$2(field(after, "close_reason")) || text$2(field(after, "reason"));
23330
24082
  if (!reason || [
23331
24083
  "closed",
23332
24084
  "open",
@@ -23340,12 +24092,12 @@ function beadActivitySummary(event) {
23340
24092
  case "label_added":
23341
24093
  case "label_removed": {
23342
24094
  let verb = event.type === "label_added" ? "added" : "removed";
23343
- let value = text$3(field(after ?? before, "label")) || text$3(payload$1(event.comment)).replace(/^(?:Added|Removed) label:\s*/i, "");
24095
+ let value = text$2(field(after ?? before, "label")) || text$2(payload$1(event.comment)).replace(/^(?:Added|Removed) label:\s*/i, "");
23344
24096
  return `${verb} a label${value ? `: ${value}` : ""}`;
23345
24097
  }
23346
24098
  case "dependency_added":
23347
24099
  case "dependency_removed": {
23348
- let value = text$3(after ?? before);
24100
+ let value = text$2(after ?? before);
23349
24101
  let match = /^(?:Added|Removed) dependency: \S+ (\S+) (\S+)$/.exec(value);
23350
24102
  let target = match?.[2] ?? (/^[\w.-]+$/.test(value) ? value : "");
23351
24103
  let kind = match?.[1];
@@ -24436,7 +25188,7 @@ function validate$1(parse, reason = "protocol") {
24436
25188
  })
24437
25189
  });
24438
25190
  }
24439
- function section$2(source, map) {
25191
+ function section$1(source, map) {
24440
25192
  return source.state === "available" ? {
24441
25193
  ...source,
24442
25194
  items: source.items.map(map)
@@ -24591,7 +25343,7 @@ var beadsStoreWorkAdapterLayer = effect(WorkAdapter, gen(function* () {
24591
25343
  return yield* validate$1(() => parseWorkDetail({
24592
25344
  connection: beadsWorkConnection(session.connection, schema),
24593
25345
  item: item(issue, schema, ready.ids.includes(id) ? true : ready.truncated ? void 0 : false),
24594
- discussion: section$2(discussion, (message) => ({
25346
+ discussion: section$1(discussion, (message) => ({
24595
25347
  id: message.id,
24596
25348
  body: message.body,
24597
25349
  createdAt: message.createdAt,
@@ -24617,13 +25369,13 @@ var beadsStoreWorkAdapterLayer = effect(WorkAdapter, gen(function* () {
24617
25369
  let events = yield* session.events(id);
24618
25370
  data = yield* validate$1(() => ({
24619
25371
  kind: "activity",
24620
- data: section$2(events, beadsWorkActivity)
25372
+ data: section$1(events, beadsWorkActivity)
24621
25373
  }));
24622
25374
  } else if (sectionId === "history") {
24623
25375
  let history = yield* session.history(id);
24624
25376
  data = yield* validate$1(() => ({
24625
25377
  kind: "history",
24626
- data: section$2(history, (entry) => ({
25378
+ data: section$1(history, (entry) => ({
24627
25379
  revision: entry.commit,
24628
25380
  author: entry.author,
24629
25381
  at: entry.createdAt,
@@ -24958,7 +25710,7 @@ function parseThreadRecord(value) {
24958
25710
  if (initial.parentId !== null) throw new WorkValidationError("thread.metadata.initial.parentId");
24959
25711
  let bindings = unique(unique(array$1(input.bindings, "bindings", binding, MAX_BEADS_THREAD_BYTES), (entry) => entry.id, "bindings.id"), (entry) => entry.nativeId, "bindings.nativeId");
24960
25712
  if (input.source === void 0 !== (input.sourceId === void 0)) throw new WorkValidationError("thread.metadata.source");
24961
- if (input.source !== void 0 && (!text$4(input.source, "thread.metadata.source", 512).trim() || !text$4(input.sourceId, "thread.metadata.sourceId", 512).trim())) throw new WorkValidationError("thread.metadata.source");
25713
+ 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");
24962
25714
  return {
24963
25715
  version: 1,
24964
25716
  kind,
@@ -24969,8 +25721,8 @@ function parseThreadRecord(value) {
24969
25721
  createdAt: timestamp(input.createdAt, "thread.metadata.createdAt"),
24970
25722
  updatedAt: timestamp(input.updatedAt, "thread.metadata.updatedAt"),
24971
25723
  ...input.source === void 0 ? {} : {
24972
- source: text$4(input.source, "thread.metadata.source", 512),
24973
- sourceId: text$4(input.sourceId, "thread.metadata.sourceId", 512)
25724
+ source: text$3(input.source, "thread.metadata.source", 512),
25725
+ sourceId: text$3(input.sourceId, "thread.metadata.sourceId", 512)
24974
25726
  }
24975
25727
  };
24976
25728
  }
@@ -26355,7 +27107,7 @@ function identifier(value) {
26355
27107
  if (typeof value !== "string" || value.length > 512 || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value)) throw failure$4("input", "Select a valid task ID.");
26356
27108
  return value;
26357
27109
  }
26358
- function text$2(value, field, max, required = false) {
27110
+ function text$1(value, field, max, required = false) {
26359
27111
  if (typeof value !== "string" || value.length > max || value.includes("\0")) throw failure$4("input", `The ${field} is invalid.`);
26360
27112
  if (required && !value.trim()) throw failure$4("input", `The ${field} is required.`);
26361
27113
  return value;
@@ -26365,7 +27117,7 @@ function readFailure(cause) {
26365
27117
  if (cause instanceof Error && /no such table/i.test(cause.message)) return failure$4("not-initialized", "This D1 database has no Beads schema. Run migrateBeadsD1 before using it.", { cause });
26366
27118
  return failure$4("command", "Unable to read project tasks from the D1 store.", { cause });
26367
27119
  }
26368
- async function select$1(core, sql, params = []) {
27120
+ async function select(core, sql, params = []) {
26369
27121
  try {
26370
27122
  return (await core.db.prepare(sql).bind(...params).all()).results;
26371
27123
  } catch (cause) {
@@ -26456,10 +27208,10 @@ function edgeJson(row) {
26456
27208
  * @returns Rows ordered by priority, newest first.
26457
27209
  */
26458
27210
  async function listLabel(core, query) {
26459
- return (await select$1(core, `SELECT ${columns}, ${edges} FROM beads_issues i
27211
+ return (await select(core, `SELECT ${columns}, ${edges} FROM beads_issues i
26460
27212
  WHERE EXISTS (SELECT 1 FROM beads_labels l WHERE l.issue_id = i.id AND l.label = ?)
26461
27213
  ${query.includeInfra ? "" : `AND ${board$1}`}
26462
- ORDER BY i.priority, i.created_at DESC, i.id LIMIT ?`, [text$2(query.label, "label", 512, true), Math.max(0, Math.trunc(query.limit))])).map((row) => listJson(row, true));
27214
+ ORDER BY i.priority, i.created_at DESC, i.id LIMIT ?`, [text$1(query.label, "label", 512, true), Math.max(0, Math.trunc(query.limit))])).map((row) => listJson(row, true));
26463
27215
  }
26464
27216
  /**
26465
27217
  * Lists durable work items as brief board rows, like `bd list --all --flat --brief`.
@@ -26468,7 +27220,7 @@ async function listLabel(core, query) {
26468
27220
  * @returns Rows ordered by priority, newest first.
26469
27221
  */
26470
27222
  async function listBoard(core, limit) {
26471
- return (await select$1(core, `SELECT ${columns}, ${edges} FROM beads_issues i WHERE ${board$1}
27223
+ return (await select(core, `SELECT ${columns}, ${edges} FROM beads_issues i WHERE ${board$1}
26472
27224
  ORDER BY i.priority, i.created_at DESC, i.id LIMIT ?`, [limit])).map((row) => listJson(row, false));
26473
27225
  }
26474
27226
  /**
@@ -26478,7 +27230,7 @@ async function listBoard(core, limit) {
26478
27230
  * @returns Rows ordered by priority, newest first.
26479
27231
  */
26480
27232
  async function listReady(core, limit) {
26481
- return (await select$1(core, `SELECT ${columns}, ${edges} FROM beads_issues i
27233
+ return (await select(core, `SELECT ${columns}, ${edges} FROM beads_issues i
26482
27234
  WHERE ${board$1} AND i.status = 'open'
26483
27235
  AND (i.defer_until IS NULL OR julianday(i.defer_until) <= julianday(?))
26484
27236
  AND NOT EXISTS (
@@ -26556,7 +27308,7 @@ async function listComments(core, id, limit) {
26556
27308
  * @returns Native events.
26557
27309
  */
26558
27310
  async function listEvents(core, id, limit) {
26559
- return (await select$1(core, `SELECT id, issue_id, event_type, actor, old_value, new_value, comment, created_at
27311
+ return (await select(core, `SELECT id, issue_id, event_type, actor, old_value, new_value, comment, created_at
26560
27312
  FROM beads_events WHERE issue_id = ? ORDER BY seq DESC LIMIT ?`, [id, limit])).map((row) => {
26561
27313
  let out = {
26562
27314
  id: row.id,
@@ -26702,12 +27454,12 @@ function revision$1(issue, expected) {
26702
27454
  return Number(expected);
26703
27455
  }
26704
27456
  async function exists$1(core, id) {
26705
- return (await select$1(core, "SELECT 1 AS found FROM beads_issues WHERE id = ?", [id])).length > 0;
27457
+ return (await select(core, "SELECT 1 AS found FROM beads_issues WHERE id = ?", [id])).length > 0;
26706
27458
  }
26707
27459
  var alphabet$1 = "0123456789abcdefghijklmnopqrstuvwxyz";
26708
27460
  async function allocate(core, options) {
26709
27461
  if (options.parentId) {
26710
- let children = await select$1(core, "SELECT id FROM beads_issues WHERE id LIKE ? ESCAPE '\\'", [`${options.parentId.replace(/[\\%_]/g, (match) => `\\${match}`)}.%`]);
27462
+ let children = await select(core, "SELECT id FROM beads_issues WHERE id LIKE ? ESCAPE '\\'", [`${options.parentId.replace(/[\\%_]/g, (match) => `\\${match}`)}.%`]);
26711
27463
  let next = 1;
26712
27464
  for (let row of children) {
26713
27465
  let suffix = String(row.id).slice(options.parentId.length + 1);
@@ -26751,7 +27503,7 @@ async function checkEdge(core, id, target, type) {
26751
27503
  if (target === id) throw failure$4("cycle", "This relationship would create a dependency cycle. Choose a different task.");
26752
27504
  if (!await exists$1(core, target)) throw failure$4("not-found", "This task or relationship target no longer exists. Refresh before trying again.");
26753
27505
  if (!blockingTypes.includes(type)) return;
26754
- if ((await select$1(core, `WITH RECURSIVE reach(id) AS (
27506
+ if ((await select(core, `WITH RECURSIVE reach(id) AS (
26755
27507
  SELECT ? UNION
26756
27508
  SELECT d.depends_on_id FROM beads_dependencies d JOIN reach r ON d.issue_id = r.id
26757
27509
  WHERE d.type IN ${blocking})
@@ -26981,7 +27733,7 @@ function reopenPlan(plan, _issue, reason, expected) {
26981
27733
  }
26982
27734
  function labels(values) {
26983
27735
  return values.map((label) => {
26984
- let value = text$2(label, "label", 512, true);
27736
+ let value = text$1(label, "label", 512, true);
26985
27737
  if (/[\r\n]/.test(value)) throw failure$4("input", "The label is invalid.");
26986
27738
  return value;
26987
27739
  });
@@ -26997,9 +27749,9 @@ function metadataObject(value) {
26997
27749
  * @returns The native row, as `bd create --json` prints it.
26998
27750
  */
26999
27751
  async function createRecord(core, value) {
27000
- let title = text$2(value.title, "title", 1e3, true);
27001
- let type = knownType(text$2(value.type, "type", 128, true));
27002
- let actor = text$2(value.actor, "actor", 512, true);
27752
+ let title = text$1(value.title, "title", 1e3, true);
27753
+ let type = knownType(text$1(value.type, "type", 128, true));
27754
+ let actor = text$1(value.actor, "actor", 512, true);
27003
27755
  let names = labels(value.labels);
27004
27756
  let metadata = metadataObject(value.metadata);
27005
27757
  let wisp = type === "message";
@@ -27047,8 +27799,8 @@ async function createRecord(core, value) {
27047
27799
  */
27048
27800
  async function addComment(core, id, value) {
27049
27801
  identifier(id);
27050
- let author = text$2(value.author, "author", 512, true);
27051
- let body = text$2(value.body, "message", 4194304);
27802
+ let author = text$1(value.author, "author", 512, true);
27803
+ let body = text$1(value.body, "message", 4194304);
27052
27804
  let plan = new Plan(core, id, core.now(), author);
27053
27805
  let comment = {
27054
27806
  id: crypto.randomUUID(),
@@ -27075,7 +27827,7 @@ async function addComment(core, id, value) {
27075
27827
  async function addDependency(core, id, target, type) {
27076
27828
  identifier(id);
27077
27829
  identifier(target);
27078
- let kind = text$2(type, "relationship type", 128, true);
27830
+ let kind = text$1(type, "relationship type", 128, true);
27079
27831
  if (!/^[a-z][a-z-]*$/.test(kind)) throw failure$4("input", "The relationship type is invalid.");
27080
27832
  if (!await exists$1(core, id)) throw failure$4("not-found", messages.missing);
27081
27833
  await checkEdge(core, id, target, kind);
@@ -27097,7 +27849,7 @@ async function addDependency(core, id, target, type) {
27097
27849
  async function removeDependency(core, id, target) {
27098
27850
  identifier(id);
27099
27851
  identifier(target);
27100
- if (!(await select$1(core, "SELECT 1 AS found FROM beads_dependencies WHERE issue_id = ? AND depends_on_id = ?", [id, target])).length) throw failure$4("not-found", "This relationship no longer exists. Refresh before trying again.");
27852
+ if (!(await select(core, "SELECT 1 AS found FROM beads_dependencies WHERE issue_id = ? AND depends_on_id = ?", [id, target])).length) throw failure$4("not-found", "This relationship no longer exists. Refresh before trying again.");
27101
27853
  let plan = new Plan(core, id, core.now());
27102
27854
  plan.update({});
27103
27855
  plan.removeEdge(target);
@@ -27115,9 +27867,9 @@ async function removeDependency(core, id, target) {
27115
27867
  */
27116
27868
  async function promote(core, id, value) {
27117
27869
  identifier(id);
27118
- let plan = new Plan(core, id, core.now(), text$2(value.actor, "actor", 512, true));
27870
+ let plan = new Plan(core, id, core.now(), text$1(value.actor, "actor", 512, true));
27119
27871
  plan.update({ ephemeral: 0 });
27120
- plan.event("promoted", { comment: text$2(value.reason, "reason", 4096) });
27872
+ plan.event("promoted", { comment: text$1(value.reason, "reason", 4096) });
27121
27873
  await apply(core, plan);
27122
27874
  }
27123
27875
  /**
@@ -27130,7 +27882,7 @@ async function close(core, id, reason) {
27130
27882
  identifier(id);
27131
27883
  let issue = await required(core, id);
27132
27884
  let plan = new Plan(core, id, core.now());
27133
- closePlan(core, plan, issue, text$2(reason, "reason", 4096));
27885
+ closePlan(core, plan, issue, text$1(reason, "reason", 4096));
27134
27886
  await apply(core, plan);
27135
27887
  }
27136
27888
  /**
@@ -27144,7 +27896,7 @@ async function reopen(core, id, reason) {
27144
27896
  let issue = await required(core, id);
27145
27897
  if (issue.status !== "closed") return;
27146
27898
  let plan = new Plan(core, id, core.now());
27147
- reopenPlan(plan, issue, text$2(reason, "reason", 4096));
27899
+ reopenPlan(plan, issue, text$1(reason, "reason", 4096));
27148
27900
  await apply(core, plan);
27149
27901
  }
27150
27902
  /**
@@ -27307,7 +28059,7 @@ async function importJsonl(core, jsonl) {
27307
28059
  merged: 0
27308
28060
  };
27309
28061
  for (let record of records) {
27310
- let [existing] = await select$1(core, "SELECT updated_at FROM beads_issues WHERE id = ?", [record.issue.id]);
28062
+ let [existing] = await select(core, "SELECT updated_at FROM beads_issues WHERE id = ?", [record.issue.id]);
27311
28063
  let plan = new Plan(core, record.issue.id, at, record.issue.createdBy ?? core.actor);
27312
28064
  if (!existing) {
27313
28065
  plan.insert(record.values);
@@ -27392,7 +28144,7 @@ function protocol(operation, parse) {
27392
28144
  })
27393
28145
  });
27394
28146
  }
27395
- function section$1(items, limit) {
28147
+ function section(items, limit) {
27396
28148
  return {
27397
28149
  state: "available",
27398
28150
  items: items.slice(0, limit),
@@ -27547,13 +28299,13 @@ function makeD1BeadsStore(options) {
27547
28299
  comments: (value, parse) => id(value).pipe(flatMap$1(() => run(() => listComments(store, value))), flatMap$1((rows) => protocol("comments", () => parse(rows)))),
27548
28300
  ready: readyIssues,
27549
28301
  issue,
27550
- discussion: (value) => id(value).pipe(flatMap$1(() => run(() => listComments(store, value, 201))), flatMap$1((rows) => protocol("comments", () => section$1(parseCliDiscussion(rows), 200)))),
28302
+ discussion: (value) => id(value).pipe(flatMap$1(() => run(() => listComments(store, value, 201))), flatMap$1((rows) => protocol("comments", () => section(parseCliDiscussion(rows), 200)))),
27551
28303
  history: () => succeed$2({
27552
28304
  state: "unsupported",
27553
28305
  items: [],
27554
28306
  truncated: false
27555
28307
  }),
27556
- events: (value) => id(value).pipe(flatMap$1(() => run(() => listEvents(store, value, 51))), flatMap$1((rows) => protocol("history", () => section$1(parseCliEvents(rows), 50)))),
28308
+ events: (value) => id(value).pipe(flatMap$1(() => run(() => listEvents(store, value, 51))), flatMap$1((rows) => protocol("history", () => section(parseCliEvents(rows), 50)))),
27557
28309
  snapshot: gen(function* () {
27558
28310
  let issues = yield* run(() => listBoard(store, MAX_BEADS + 1)).pipe(flatMap$1((rows) => protocol("list", () => parseCliIssues(rows))));
27559
28311
  let unblocked = yield* readyIssues(MAX_BEADS + 1);
@@ -35942,1021 +36694,381 @@ var watchNode = (path, info, options) => callback((queue) => acquireRelease(sync
35942
36694
  return;
35943
36695
  case "change":
35944
36696
  offerUnsafe(queue, {
35945
- _tag: "Update",
35946
- path
35947
- });
35948
- return;
35949
- }
35950
- });
35951
- watcher.on("error", (error) => {
35952
- failCauseUnsafe(queue, fail$7(systemError({
35953
- module: "FileSystem",
35954
- _tag: "Unknown",
35955
- method: "watch",
35956
- pathOrDescriptor: path,
35957
- cause: error
35958
- })));
35959
- });
35960
- watcher.on("close", () => {
35961
- endUnsafe(queue);
35962
- });
35963
- return watcher;
35964
- }), (watcher) => sync(() => watcher.close())).pipe(tap$1(() => sync(() => options?.onReady?.()))));
35965
- var watch = (backend, path, options) => all$1([stat$1(path), serviceOption(WatchBackend)]).pipe(map$2(([stat, callerBackend]) => orElse(callerBackend, () => backend).pipe(flatMap$5((_) => _.register(path, stat, options)), getOrElse(() => watchNode(path, stat, options)))), unwrap$1);
35966
- var writeFile$1 = (path, data, options) => callback$1((resume, signal) => {
35967
- try {
35968
- NFS.writeFile(path, data, {
35969
- signal,
35970
- flag: options?.flag,
35971
- mode: options?.mode
35972
- }, (err) => {
35973
- if (err) resume(fail$6(handleErrnoException("FileSystem", "writeFile")(err, [path])));
35974
- else resume(void_);
35975
- });
35976
- } catch (err) {
35977
- resume(fail$6(handleBadArgument("writeFile")(err)));
35978
- }
35979
- });
35980
- var makeFileSystem = /*#__PURE__*/ map$2(/*#__PURE__*/ serviceOption(WatchBackend), (backend) => make$14({
35981
- access: access$1,
35982
- chmod,
35983
- chown,
35984
- copy,
35985
- copyFile,
35986
- glob,
35987
- link,
35988
- makeDirectory,
35989
- makeTempDirectory,
35990
- makeTempDirectoryScoped,
35991
- makeTempFile,
35992
- makeTempFileScoped,
35993
- open,
35994
- readDirectory,
35995
- readFile: readFile$1,
35996
- readLink,
35997
- realPath,
35998
- remove,
35999
- rename: rename$1,
36000
- stat: stat$1,
36001
- symlink: symlink$1,
36002
- truncate,
36003
- utimes,
36004
- watch(path, options) {
36005
- return watch(backend, path, options);
36006
- },
36007
- writeFile: writeFile$1
36008
- }));
36009
- //#endregion
36010
- //#region ../../node_modules/.pnpm/@effect+platform-node@4.0.5_effect@4.0.5/node_modules/@effect/platform-node/dist/NodeFileSystem.js
36011
- /**
36012
- * Node.js `FileSystem` layer for programs that perform real filesystem I/O.
36013
- *
36014
- * The exported layer satisfies the platform-independent `FileSystem` service
36015
- * with Node-backed operations for files, directories, metadata, permissions,
36016
- * links, temporary paths, and path watching. Effects still call the service from
36017
- * `effect/FileSystem`; this module only chooses the Node implementation.
36018
- *
36019
- * @stability unstable
36020
- * @since 4.0.0
36021
- */
36022
- /**
36023
- * Provides the `FileSystem` service backed by Node filesystem APIs.
36024
- *
36025
- * @stability unstable
36026
- * @category layers
36027
- * @since 4.0.0
36028
- */
36029
- var layer$2 = /* @__PURE__ */ effect(FileSystem)(makeFileSystem);
36030
- //#endregion
36031
- //#region ../../node_modules/.pnpm/@effect+platform-node-shared@4.0.5_effect@4.0.5/node_modules/@effect/platform-node-shared/dist/NodePath.js
36032
- /**
36033
- * Node-backed provider for Effect's `Path` service.
36034
- *
36035
- * This module turns Node's `node:path` and `node:url` APIs into `Path` layers.
36036
- * `layer` uses the host platform path implementation, while `layerPosix` and
36037
- * `layerWin32` provide fixed POSIX and Windows variants. All three layers also
36038
- * include helpers for converting between file paths and file URLs.
36039
- *
36040
- * @stability unstable
36041
- * @since 4.0.0
36042
- */
36043
- var fileUrlOps = (windows) => ({
36044
- fromFileUrl: (url) => try_({
36045
- try: () => NodeUrl.fileURLToPath(url, { windows }),
36046
- catch: (cause) => new BadArgument({
36047
- module: "Path",
36048
- method: "fromFileUrl",
36049
- cause
36050
- })
36051
- }),
36052
- toFileUrl: (path) => try_({
36053
- try: () => NodeUrl.pathToFileURL(path, { windows }),
36054
- catch: (cause) => new BadArgument({
36055
- module: "Path",
36056
- method: "toFileUrl",
36057
- cause
36058
- })
36059
- })
36060
- });
36061
- ({
36062
- ...Path.posix,
36063
- .../*#__PURE__*/ fileUrlOps(false)
36064
- });
36065
- ({
36066
- ...Path.win32,
36067
- .../*#__PURE__*/ fileUrlOps(true)
36068
- });
36069
- provideMerge(layer$4, mergeAll$1(layer$2, /* @__PURE__ */ succeed$3(Path$1)(/*#__PURE__*/ make$13({
36070
- ...Path,
36071
- .../*#__PURE__*/ fileUrlOps(void 0)
36072
- }))));
36073
- TaggedError("GitInputError");
36074
- TaggedError("GitCommandError");
36075
- TaggedError("GitRepositoryError");
36076
- TaggedError("GitIdentityError");
36077
- TaggedError("GitProtocolError");
36078
- TaggedError("GitConflictError");
36079
- TaggedError("GitIndexError");
36080
- //#endregion
36081
- //#region ../git/src/lib/validation.ts
36082
- var text$1 = String$1.check(isNonEmpty(), isPattern(/^[^\0]*$/));
36083
- var oid = String$1.check(isPattern(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/));
36084
- var path$1 = text$1.check(makeFilter((value) => !value.startsWith("/") && !value.includes("\\") && value.split("/").every((part) => part.length > 0 && part !== "." && part !== ".." && part.toLowerCase() !== ".git")));
36085
- var ref$1 = text$1.check(makeFilter((value) => value.startsWith("refs/") && !/[\x00-\x20\x7f~^:?*\[\\]/.test(value) && !value.includes("..") && !value.includes("@{") && !value.endsWith(".") && value.split("/").every((part) => part.length > 0 && !part.startsWith(".") && !part.endsWith(".lock"))));
36086
- var revision = Union([
36087
- oid,
36088
- ref$1,
36089
- Literal("HEAD")
36090
- ]);
36091
- String$1.check(isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]*$/));
36092
- var mode = Literals([
36093
- "100644",
36094
- "100755",
36095
- "120000"
36096
- ]);
36097
- Struct({
36098
- name: text$1.check(isPattern(/^[^\r\n<>]+$/)),
36099
- email: text$1.check(isPattern(/^[^\r\n<>]+$/))
36100
- });
36101
- Struct({
36102
- ref: ref$1,
36103
- expectedHead: NullOr(oid),
36104
- files: ArraySchema(Struct({
36105
- path: path$1,
36106
- contents: Union([
36107
- String$1,
36108
- Uint8Array$1,
36109
- Null
36110
- ]),
36111
- mode: optional$1(mode)
36112
- })).check(isMaxLength(1e4)),
36113
- message: text$1,
36114
- extraParents: optional$1(ArraySchema(oid).check(isMaxLength(16)))
36115
- });
36116
- Struct({
36117
- ...Struct({
36118
- base: optional$1(oid),
36119
- target: optional$1(oid),
36120
- staged: optional$1(Boolean$1),
36121
- paths: optional$1(ArraySchema(path$1).check(isMaxLength(1e4)))
36122
- }).fields,
36123
- context: optional$1(Int$1.check(isBetween({
36124
- minimum: 0,
36125
- maximum: 1e3
36126
- })))
36127
- });
36128
- Struct({ environment: optional$1(Record(String$1, Union([String$1, Undefined]))) });
36129
- Struct({
36130
- commit: oid,
36131
- path: path$1,
36132
- limit: Int$1.check(isBetween({
36133
- minimum: 1,
36134
- maximum: 101
36135
- })),
36136
- skip: optional$1(Int$1.check(isBetween({
36137
- minimum: 0,
36138
- maximum: 1e4
36139
- })))
36140
- });
36141
- Struct({
36142
- from: revision,
36143
- limit: Int$1.check(isBetween({
36144
- minimum: 1,
36145
- maximum: 500
36146
- }))
36147
- });
36148
- Int$1.check(isBetween({
36149
- minimum: 1,
36150
- maximum: 1e3
36151
- }));
36152
- //#endregion
36153
- //#region ../git/src/lib/git.ts
36154
- /** Opens the Git repository that contains a directory. */
36155
- var Git = class extends Service()("@bud.sh/git/Git") {};
36156
- //#endregion
36157
- //#region ../live/src/server/hub.ts
36158
- /**
36159
- * Orders every published change into one stream per companion. Clients resume from their
36160
- * last event id; anything older than the replay buffer, or from an earlier companion,
36161
- * receives a resync instead.
36162
- */
36163
- var LiveHub = class {
36164
- epoch = randomBytes(8).toString("hex");
36165
- #sequence = 0;
36166
- #buffer = [];
36167
- #replay;
36168
- #heartbeatMs;
36169
- #listeners = /* @__PURE__ */ new Set();
36170
- constructor(options = {}) {
36171
- this.#replay = options.replay ?? 1024;
36172
- this.#heartbeatMs = options.heartbeatMs ?? 15e3;
36173
- }
36174
- /**
36175
- * Sends a change to every connected client.
36176
- * @param topic - The domain, such as `previews` or `files`.
36177
- * @param data - JSON data each domain validates with its own schema.
36178
- */
36179
- publish(topic, data) {
36180
- let event = {
36181
- id: `${this.epoch}:${++this.#sequence}`,
36182
- topic,
36183
- data
36184
- };
36185
- this.#buffer.push(event);
36186
- if (this.#buffer.length > this.#replay) this.#buffer.shift();
36187
- for (let listener of [...this.#listeners]) listener(event);
36188
- }
36189
- /**
36190
- * Opens one client's `text/event-stream`.
36191
- * @param lastEventId - The client's `Last-Event-ID`, if it is resuming.
36192
- * @param signal - The request's lifetime; aborting closes the stream.
36193
- * @returns The response body.
36194
- */
36195
- open(lastEventId, signal) {
36196
- let encoder = new TextEncoder();
36197
- let close = () => {};
36198
- return new ReadableStream({
36199
- start: (controller) => {
36200
- let send = (text) => controller.enqueue(encoder.encode(text));
36201
- let write = (event) => send(`id: ${event.id}\nevent: ${event.topic}\ndata: ${JSON.stringify(event.data)}\n\n`);
36202
- let missed = this.#since(lastEventId);
36203
- if (missed) for (let event of missed) write(event);
36204
- else write({
36205
- id: `${this.epoch}:${this.#sequence}`,
36206
- topic: RESYNC,
36207
- data: null
36208
- });
36209
- this.#listeners.add(write);
36210
- let heartbeat = setInterval(() => send(": heartbeat\n\n"), this.#heartbeatMs);
36211
- close = () => {
36212
- clearInterval(heartbeat);
36213
- this.#listeners.delete(write);
36214
- try {
36215
- controller.close();
36216
- } catch {}
36217
- };
36218
- if (signal.aborted) close();
36219
- else signal.addEventListener("abort", close, { once: true });
36220
- },
36221
- cancel: () => close()
36697
+ _tag: "Update",
36698
+ path
36699
+ });
36700
+ return;
36701
+ }
36702
+ });
36703
+ watcher.on("error", (error) => {
36704
+ failCauseUnsafe(queue, fail$7(systemError({
36705
+ module: "FileSystem",
36706
+ _tag: "Unknown",
36707
+ method: "watch",
36708
+ pathOrDescriptor: path,
36709
+ cause: error
36710
+ })));
36711
+ });
36712
+ watcher.on("close", () => {
36713
+ endUnsafe(queue);
36714
+ });
36715
+ return watcher;
36716
+ }), (watcher) => sync(() => watcher.close())).pipe(tap$1(() => sync(() => options?.onReady?.()))));
36717
+ var watch = (backend, path, options) => all$1([stat$1(path), serviceOption(WatchBackend)]).pipe(map$2(([stat, callerBackend]) => orElse(callerBackend, () => backend).pipe(flatMap$5((_) => _.register(path, stat, options)), getOrElse(() => watchNode(path, stat, options)))), unwrap$1);
36718
+ var writeFile$1 = (path, data, options) => callback$1((resume, signal) => {
36719
+ try {
36720
+ NFS.writeFile(path, data, {
36721
+ signal,
36722
+ flag: options?.flag,
36723
+ mode: options?.mode
36724
+ }, (err) => {
36725
+ if (err) resume(fail$6(handleErrnoException("FileSystem", "writeFile")(err, [path])));
36726
+ else resume(void_);
36222
36727
  });
36728
+ } catch (err) {
36729
+ resume(fail$6(handleBadArgument("writeFile")(err)));
36223
36730
  }
36224
- #since(lastEventId) {
36225
- let last = lastEventId ? parseLiveId(lastEventId) : null;
36226
- if (!last || last.epoch !== this.epoch || last.sequence > this.#sequence) return null;
36227
- let oldest = this.#buffer[0] ? parseLiveId(this.#buffer[0].id).sequence : this.#sequence + 1;
36228
- if (last.sequence < oldest - 1) return null;
36229
- return this.#buffer.filter((event) => parseLiveId(event.id).sequence > last.sequence);
36230
- }
36231
- };
36731
+ });
36732
+ var makeFileSystem = /*#__PURE__*/ map$2(/*#__PURE__*/ serviceOption(WatchBackend), (backend) => make$14({
36733
+ access: access$1,
36734
+ chmod,
36735
+ chown,
36736
+ copy,
36737
+ copyFile,
36738
+ glob,
36739
+ link,
36740
+ makeDirectory,
36741
+ makeTempDirectory,
36742
+ makeTempDirectoryScoped,
36743
+ makeTempFile,
36744
+ makeTempFileScoped,
36745
+ open,
36746
+ readDirectory,
36747
+ readFile: readFile$1,
36748
+ readLink,
36749
+ realPath,
36750
+ remove,
36751
+ rename: rename$1,
36752
+ stat: stat$1,
36753
+ symlink: symlink$1,
36754
+ truncate,
36755
+ utimes,
36756
+ watch(path, options) {
36757
+ return watch(backend, path, options);
36758
+ },
36759
+ writeFile: writeFile$1
36760
+ }));
36232
36761
  //#endregion
36233
- //#region ../live/src/server/service.ts
36234
- /** The companion's ordered live channel: publishes changes and opens client streams. */
36235
- var LiveChannel = class extends Service()("@bud.sh/live/LiveChannel") {};
36762
+ //#region ../../node_modules/.pnpm/@effect+platform-node@4.0.5_effect@4.0.5/node_modules/@effect/platform-node/dist/NodeFileSystem.js
36236
36763
  /**
36237
- * One ordered live channel per companion.
36238
- * @param options - Replay buffer and heartbeat settings.
36239
- * @returns The LiveChannel layer.
36764
+ * Node.js `FileSystem` layer for programs that perform real filesystem I/O.
36765
+ *
36766
+ * The exported layer satisfies the platform-independent `FileSystem` service
36767
+ * with Node-backed operations for files, directories, metadata, permissions,
36768
+ * links, temporary paths, and path watching. Effects still call the service from
36769
+ * `effect/FileSystem`; this module only chooses the Node implementation.
36770
+ *
36771
+ * @stability unstable
36772
+ * @since 4.0.0
36240
36773
  */
36241
- function liveChannelLayer(options = {}) {
36242
- return sync$1(LiveChannel, () => {
36243
- let hub = new LiveHub(options);
36244
- return {
36245
- epoch: hub.epoch,
36246
- publish: (topic, data) => sync(() => hub.publish(topic, data)),
36247
- open: (lastEventId, signal) => hub.open(lastEventId, signal)
36248
- };
36249
- });
36250
- }
36251
36774
  /**
36252
- * Publishes every element of a domain's change stream on the live channel, for the lifetime
36253
- * of the layer that forks it.
36254
- * @param topic - The topic clients subscribe to.
36255
- * @param changes - The domain's change stream.
36256
- * @returns An effect to fork in a scoped layer.
36775
+ * Provides the `FileSystem` service backed by Node filesystem APIs.
36776
+ *
36777
+ * @stability unstable
36778
+ * @category layers
36779
+ * @since 4.0.0
36257
36780
  */
36258
- function publishChanges(topic, changes) {
36259
- return gen(function* () {
36260
- let channel = yield* LiveChannel;
36261
- yield* changes.pipe(runForEach((change) => channel.publish(topic, change)), ignore$2, forkScoped);
36262
- });
36263
- }
36264
- //#endregion
36265
- //#region ../project/src/server/errors.ts
36266
- /** Expected project startup/configuration failure; nested causes remain server-only. */
36267
- var ProjectError = class extends TaggedError("ProjectError") {};
36781
+ var layer$2 = /* @__PURE__ */ effect(FileSystem)(makeFileSystem);
36268
36782
  //#endregion
36269
- //#region ../project/src/server/modules.ts
36783
+ //#region ../../node_modules/.pnpm/@effect+platform-node-shared@4.0.5_effect@4.0.5/node_modules/@effect/platform-node-shared/dist/NodePath.js
36270
36784
  /**
36271
- * Confines a validated config module to a regular, bounded file in its project.
36272
- * @param root - The trusted consumer directory.
36273
- * @param module - A project-relative module already validated by the config resolver.
36274
- * @param options - The config setting and whether its source runs on the server or browser.
36275
- * @returns The canonical filename, without evaluating its code.
36785
+ * Node-backed provider for Effect's `Path` service.
36786
+ *
36787
+ * This module turns Node's `node:path` and `node:url` APIs into `Path` layers.
36788
+ * `layer` uses the host platform path implementation, while `layerPosix` and
36789
+ * `layerWin32` provide fixed POSIX and Windows variants. All three layers also
36790
+ * include helpers for converting between file paths and file URLs.
36791
+ *
36792
+ * @stability unstable
36793
+ * @since 4.0.0
36276
36794
  */
36277
- function projectModuleFile(root, module, options) {
36278
- return gen(function* () {
36279
- let fs = yield* FileSystem;
36280
- let path = yield* Path$1;
36281
- let { setting, kind } = options;
36282
- let project = yield* fs.realPath(root).pipe(mapError$2((cause) => new ProjectError({
36283
- reason: "root",
36284
- message: "Cannot open the module project root.",
36285
- cause
36286
- })));
36287
- let file = yield* fs.realPath(path.resolve(project, module)).pipe(mapError$2((cause) => new ProjectError({
36288
- reason: "read",
36289
- message: `${setting} cannot find ${module} in the project.`,
36290
- cause
36291
- })));
36292
- let relative = path.relative(project, file);
36293
- if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) || relative.split(path.sep).includes("node_modules")) return yield* new ProjectError({
36294
- reason: "invalid",
36295
- message: `${setting} must resolve to a project-owned file outside node_modules.`
36296
- });
36297
- if (kind === "server" && !file.endsWith(".server.ts")) return yield* new ProjectError({
36298
- reason: "invalid",
36299
- message: `${setting} must resolve to a .server.ts module.`
36300
- });
36301
- if (kind === "browser" && (!/\.(?:ts|tsx|js|jsx)$/.test(file) || /\.server\.[cm]?[jt]sx?$/.test(file))) return yield* new ProjectError({
36302
- reason: "invalid",
36303
- message: `${setting} must resolve to a browser module.`
36304
- });
36305
- if ((yield* fs.stat(file).pipe(mapError$2((cause) => new ProjectError({
36306
- reason: "read",
36307
- message: `${setting} cannot inspect ${module}.`,
36795
+ var fileUrlOps = (windows) => ({
36796
+ fromFileUrl: (url) => try_({
36797
+ try: () => NodeUrl.fileURLToPath(url, { windows }),
36798
+ catch: (cause) => new BadArgument({
36799
+ module: "Path",
36800
+ method: "fromFileUrl",
36308
36801
  cause
36309
- })))).type !== "File") return yield* new ProjectError({
36310
- reason: "invalid",
36311
- message: `${setting} must be a regular module file.`
36312
- });
36313
- yield* readText(file, { maxBytes: 262144 }).pipe(mapError$2((cause) => new ProjectError({
36314
- reason: "read",
36315
- message: `${setting} must be a readable module no larger than 256 KiB.`,
36802
+ })
36803
+ }),
36804
+ toFileUrl: (path) => try_({
36805
+ try: () => NodeUrl.pathToFileURL(path, { windows }),
36806
+ catch: (cause) => new BadArgument({
36807
+ module: "Path",
36808
+ method: "toFileUrl",
36316
36809
  cause
36317
- })));
36318
- return file;
36319
- });
36320
- }
36321
- //#endregion
36322
- //#region ../project/src/lib/validate.ts
36323
- var errors = /* @__PURE__ */ new WeakMap();
36324
- /**
36325
- * Validates config, throwing its first problem as the `Error` the config checks report.
36326
- * @param schema - The config schema.
36327
- * @param value - The project's untrusted setting.
36328
- * @returns The validated, normalized setting.
36329
- */
36330
- function parseConfig(schema, value) {
36331
- let result = parseSafe(schema, value, { abortEarly: true });
36332
- if (result.success) return result.value;
36333
- let issue = result.issues[0];
36334
- throw errors.get(issue) ?? new Error(issue.message);
36335
- }
36336
- /**
36337
- * Reports any failure of `schema` with one message.
36338
- * @param schema - The schema to run.
36339
- * @param message - The message for any of its failures.
36340
- * @returns The described schema.
36341
- */
36342
- function described(schema, message) {
36343
- return createSchema((value, context) => {
36344
- let result = schema["~run"](value, context);
36345
- return result.issues ? fail(message, context.path) : result;
36346
- });
36347
- }
36348
- /**
36349
- * Applies a rule after `schema` validates.
36350
- * @param schema - The schema to run first.
36351
- * @param rule - Returns a message, or undefined when the value is valid.
36352
- * @returns The checked schema.
36353
- */
36354
- function check(schema, rule) {
36355
- return createSchema((value, context) => {
36356
- let result = schema["~run"](value, context);
36357
- if (result.issues) return result;
36358
- let message = rule(result.value);
36359
- return message === void 0 ? result : fail(message, context.path);
36360
- });
36361
- }
36362
- /**
36363
- * Validates with a function that throws, such as a URL or route map parser, keeping its error.
36364
- * @param schema - The schema to run first.
36365
- * @param parse - Turns its value into the output, or throws.
36366
- * @returns The delegating schema.
36367
- */
36368
- function delegate(schema, parse) {
36369
- return createSchema((value, context) => {
36370
- let result = schema["~run"](value, context);
36371
- if (result.issues) return result;
36372
- try {
36373
- return { value: parse(result.value) };
36374
- } catch (error) {
36375
- if (!(error instanceof Error)) throw error;
36376
- let failure = fail(error.message, context.path);
36377
- errors.set(failure.issues[0], error);
36378
- return failure;
36379
- }
36380
- });
36381
- }
36382
- /**
36383
- * Chooses a schema from the unvalidated value.
36384
- * @param choose - Picks the schema for a value.
36385
- * @returns The selecting schema.
36386
- */
36387
- function select(choose) {
36388
- return createSchema((value, context) => choose(value)["~run"](value, context));
36389
- }
36390
- /**
36391
- * Substitutes a default for null or undefined.
36392
- * @param schema - The schema for present values.
36393
- * @param value - Makes the default.
36394
- * @returns The schema with its default.
36395
- */
36396
- function fallback(schema, value) {
36397
- return createSchema((input, context) => input == null ? { value: value() } : schema["~run"](input, context));
36398
- }
36399
- /**
36400
- * A settings object that names its unsupported keys before validating its fields.
36401
- * @param name - The setting's name in messages, such as `previews`.
36402
- * @param shape - The schema for each supported key.
36403
- * @returns The settings schema.
36404
- */
36405
- function settings(name, shape) {
36406
- let schema = object(shape);
36407
- return createSchema((value, context) => {
36408
- if (!value || typeof value !== "object" || Array.isArray(value)) return fail(`${name} must be an object.`, context.path);
36409
- for (let key of Object.keys(value)) if (!Object.hasOwn(shape, key)) return fail(`${name}.${key} is not a supported setting.`, [...context.path, key]);
36410
- return schema["~run"](value, context);
36411
- });
36412
- }
36413
- /**
36414
- * A list whose length is checked before its entries.
36415
- * @param item - The schema for each entry.
36416
- * @param message - The message when it is not a list or is too short.
36417
- * @param minimum - The fewest entries it may have.
36418
- * @returns The list schema.
36419
- */
36420
- function list(item, message, minimum = 0) {
36421
- let items = array(item);
36422
- return createSchema((value, context) => !Array.isArray(value) || value.length < minimum ? fail(message, context.path) : items["~run"](value, context));
36423
- }
36424
- //#endregion
36425
- //#region ../project/src/lib/files.ts
36426
- /** The Files settings, with their defaults filled in. */
36427
- var BudFilesSchema = defaulted(settings("files", { edit: fallback(described(boolean(), "files.edit must be true or false."), () => true) }), () => ({ edit: true }));
36428
- //#endregion
36429
- //#region ../project/src/lib/flows.ts
36430
- var message$1 = "flows must be [{ id, title, start, script, steps: [{ id, title }], block? }] with short ids, local paths and unique steps.";
36431
- var slug = (value) => typeof value === "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(value);
36432
- var text = (value) => typeof value === "string" && value.trim() !== "" && value.length <= 200;
36433
- function flow(value) {
36434
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
36435
- let { id, title, start, script, steps, block = [] } = value;
36436
- if (!slug(id) || !text(title) || !text(script) || script.includes("\0")) return null;
36437
- if (typeof start !== "string" || !start.startsWith("/") || start.startsWith("//")) return null;
36438
- if (new URL(start, "http://bud.invalid").origin !== "http://bud.invalid") return null;
36439
- if (!Array.isArray(steps) || !steps.length || steps.length > 50) return null;
36440
- let resolved = steps.map((step) => {
36441
- let { id: stepId, title: stepTitle } = step ?? {};
36442
- return slug(stepId) && text(stepTitle) ? {
36443
- id: stepId,
36444
- title: stepTitle.trim()
36445
- } : null;
36446
- });
36447
- if (resolved.some((step) => !step)) return null;
36448
- let ids = resolved.map((step) => step.id);
36449
- if (new Set(ids).size !== ids.length) return null;
36450
- if (!Array.isArray(block) || block.some((part) => !text(part))) return null;
36451
- return {
36452
- id,
36453
- title: title.trim(),
36454
- start,
36455
- script: script.trim(),
36456
- steps: resolved,
36457
- block
36458
- };
36459
- }
36460
- /** The flows, none when omitted. */
36461
- var BudFlowsSchema = createSchema((value, context) => {
36462
- if (value === void 0) return { value: [] };
36463
- if (!Array.isArray(value)) return fail(message$1, context.path);
36464
- let flows = value.map(flow);
36465
- if (flows.some((resolved) => !resolved)) return fail(message$1, context.path);
36466
- let ids = flows.map((resolved) => resolved.id);
36467
- if (new Set(ids).size !== ids.length) return fail(message$1, context.path);
36468
- return { value: flows };
36469
- });
36470
- /**
36471
- * Where a flow's step is pictured: a route of its own, named `<flow>/<step>`, in a group named
36472
- * after the flow. Its path is the start page with the step as its fragment, unique among routes.
36473
- * @param flow - The flow.
36474
- * @param step - One of its steps.
36475
- * @returns The step's route.
36476
- */
36477
- function flowStepRoute(flow, step) {
36478
- return {
36479
- id: `${flow.id}/${step.id}`,
36480
- path: `${flow.start.replace(/#.*$/, "")}#${flow.id}-${step.id}`,
36481
- title: step.title,
36482
- group: flow.title,
36483
- flow: {
36484
- id: flow.id,
36485
- step: step.id
36486
- }
36487
- };
36488
- }
36489
- //#endregion
36490
- //#region ../project/src/lib/logo.ts
36491
- /** The image types a logo may be, by extension. */
36492
- var LOGO_TYPES = Object.freeze({
36493
- ".svg": "image/svg+xml",
36494
- ".png": "image/png",
36495
- ".webp": "image/webp",
36496
- ".jpg": "image/jpeg",
36497
- ".jpeg": "image/jpeg",
36498
- ".gif": "image/gif",
36499
- ".ico": "image/x-icon"
36810
+ })
36811
+ })
36500
36812
  });
36501
- var logoMessage = "logo must be an image path inside the project (.svg, .png, .webp, .jpg, .gif or .ico), or { light, dark }.";
36502
- function image(value) {
36503
- if (typeof value !== "string" || value.length > 1024 || value.includes("\0")) return null;
36504
- let path = value.trim().replaceAll("\\", "/");
36505
- if (!path || path.startsWith("/") || /^[a-z]:/i.test(path)) return null;
36506
- if (path.split("/").includes("..")) return null;
36507
- let extension = path.slice(path.lastIndexOf(".")).toLowerCase();
36508
- return Object.hasOwn(LOGO_TYPES, extension) ? path : null;
36509
- }
36510
- /** A logo in its one resolved form, or null for Bud's own mark. */
36511
- var BudLogoSchema = createSchema((value, context) => {
36512
- if (value === void 0) return { value: null };
36513
- let light = image(typeof value === "object" && value ? value.light : value);
36514
- let raw = typeof value === "object" && value ? value.dark : void 0;
36515
- let dark = raw === void 0 ? null : image(raw);
36516
- let keys = typeof value === "object" && value ? Object.keys(value) : [];
36517
- if (light && (raw === void 0 || dark) && keys.every((key) => key === "light" || key === "dark")) return { value: {
36518
- light,
36519
- dark
36520
- } };
36521
- return fail(logoMessage, context.path);
36813
+ ({
36814
+ ...Path.posix,
36815
+ .../*#__PURE__*/ fileUrlOps(false)
36522
36816
  });
36523
- //#endregion
36524
- //#region ../project/src/lib/branches.ts
36525
- var message = "branches must be { main?, build?: { install?, build?, serve? } } with a branch name and nonempty shell commands.";
36526
- function branch(value) {
36527
- return typeof value === "string" && value.length <= 255 && /^[A-Za-z0-9._/-]+$/.test(value) && !value.startsWith("-") && !value.startsWith("/") && !value.endsWith("/") && !value.endsWith(".lock") && !value.includes("..") && !value.includes("//") ? value : null;
36528
- }
36529
- function command(value) {
36530
- return typeof value === "string" && value.trim() && value.length <= 4096 && !value.includes("\0") ? value.trim() : null;
36531
- }
36532
- /** The branch settings, defaults filled in. */
36533
- var BudBranchesSchema = createSchema((value, context) => {
36534
- let empty = {
36535
- main: null,
36536
- install: null,
36537
- build: null,
36538
- serve: null
36539
- };
36540
- if (value === void 0) return { value: empty };
36541
- if (!value || typeof value !== "object" || Array.isArray(value)) return fail(message, context.path);
36542
- let input = value;
36543
- let build = input.build ?? {};
36544
- if (!build || typeof build !== "object" || Array.isArray(build) || Object.keys(input).some((key) => key !== "main" && key !== "build")) return fail(message, context.path);
36545
- let steps = build;
36546
- if (Object.keys(steps).some((key) => ![
36547
- "install",
36548
- "build",
36549
- "serve"
36550
- ].includes(key))) return fail(message, context.path);
36551
- let resolved = {
36552
- main: input.main === void 0 ? null : branch(input.main),
36553
- install: steps.install === void 0 ? null : command(steps.install),
36554
- build: steps.build === void 0 ? null : command(steps.build),
36555
- serve: steps.serve === void 0 ? null : command(steps.serve)
36556
- };
36557
- if (input.main !== void 0 && !resolved.main || [
36558
- "install",
36559
- "build",
36560
- "serve"
36561
- ].some((key) => steps[key] !== void 0 && !resolved[key])) return fail(message, context.path);
36562
- return { value: resolved };
36817
+ ({
36818
+ ...Path.win32,
36819
+ .../*#__PURE__*/ fileUrlOps(true)
36563
36820
  });
36821
+ provideMerge(layer$4, mergeAll$1(layer$2, /* @__PURE__ */ succeed$3(Path$1)(/*#__PURE__*/ make$13({
36822
+ ...Path,
36823
+ .../*#__PURE__*/ fileUrlOps(void 0)
36824
+ }))));
36825
+ TaggedError("GitInputError");
36826
+ TaggedError("GitCommandError");
36827
+ TaggedError("GitRepositoryError");
36828
+ TaggedError("GitIdentityError");
36829
+ TaggedError("GitProtocolError");
36830
+ TaggedError("GitConflictError");
36831
+ TaggedError("GitIndexError");
36564
36832
  //#endregion
36565
- //#region ../project/src/lib/storage.ts
36566
- /**
36567
- * A project module named in the config: a ./ path inside the project, outside node_modules.
36568
- * @param name - The config option, for the error message.
36569
- * @param server - Whether it runs on the server (.server.ts) rather than in the browser.
36570
- * @param message - What to say instead of the default when it is invalid.
36571
- * @returns A schema for the module's path.
36572
- */
36573
- function modulePath(name, server, message) {
36574
- let extension = server ? /\.server\.ts$/ : /\.(?:ts|tsx|js|jsx)$/;
36575
- return described(string().refine((value) => value.length <= 1024 && value.startsWith("./") && !/[\u0000-\u001f\u007f\\?#%]/.test(value) && extension.test(value) && (server || !/\.server\.[cm]?[jt]sx?$/.test(value)) && !value.slice(2).split("/").some((part) => !part || part === "." || part === ".." || part === "node_modules")), message ?? (server ? `${name} must be a built-in adapter or a ./ module ending in .server.ts inside the project.` : `${name} must be bud:beads or a ./ browser module ending in .ts, .tsx, .js or .jsx inside the project.`));
36576
- }
36577
- var builtInStores = enum_([
36578
- false,
36579
- "bud:beads",
36580
- "bud:memory",
36581
- "bud:git-comments"
36833
+ //#region ../git/src/lib/validation.ts
36834
+ var text = String$1.check(isNonEmpty(), isPattern(/^[^\0]*$/));
36835
+ var oid = String$1.check(isPattern(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/));
36836
+ var path$1 = text.check(makeFilter((value) => !value.startsWith("/") && !value.includes("\\") && value.split("/").every((part) => part.length > 0 && part !== "." && part !== ".." && part.toLowerCase() !== ".git")));
36837
+ var ref$1 = text.check(makeFilter((value) => value.startsWith("refs/") && !/[\x00-\x20\x7f~^:?*\[\\]/.test(value) && !value.includes("..") && !value.includes("@{") && !value.endsWith(".") && value.split("/").every((part) => part.length > 0 && !part.startsWith(".") && !part.endsWith(".lock"))));
36838
+ var revision = Union([
36839
+ oid,
36840
+ ref$1,
36841
+ Literal("HEAD")
36582
36842
  ]);
36583
- function store(kind) {
36584
- let module = modulePath(`storage.${kind}`, true);
36585
- return defaulted(check(select((value) => value === false || value === "bud:beads" || value === "bud:memory" || value === "bud:git-comments" ? builtInStores : module), (value) => kind === "work" && value === "bud:git-comments" ? "storage.work cannot use bud:git-comments; it only stores threads." : void 0), "bud:beads");
36586
- }
36587
- /** The selected work and discussion stores, with Beads as the default for both. */
36588
- var BudStorageSchema = defaulted(settings("storage", {
36589
- work: store("work"),
36590
- threads: store("threads")
36591
- }), () => ({
36592
- work: "bud:beads",
36593
- threads: "bud:beads"
36594
- }));
36595
- /** Task presentation, chosen separately from its storage. */
36596
- var BudDesignsSchema = defaulted(settings("designs", {
36597
- tasks: defaulted(select((value) => value === "bud:beads" ? enum_(["bud:beads"]) : modulePath("designs.tasks", false)), "bud:beads"),
36598
- pages: defaulted(enum_(["path", "title"]), "path")
36599
- }), () => ({
36600
- tasks: "bud:beads",
36601
- pages: "path"
36602
- }));
36603
- //#endregion
36604
- //#region ../project/src/lib/picker.ts
36605
- var builtIn = /* @__PURE__ */ new Set([
36606
- "bud:dom",
36607
- "bud:react",
36608
- "bud:react-grab"
36843
+ String$1.check(isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]*$/));
36844
+ var mode = Literals([
36845
+ "100644",
36846
+ "100755",
36847
+ "120000"
36609
36848
  ]);
36610
- var projectPicker = modulePath("picker", false, "picker must be bud:dom, bud:react, bud:react-grab or a ./ browser module ending in .ts, .tsx, .js or .jsx inside the project.");
36611
- /** The picker, with Bud's DOM picker as the default. */
36612
- var BudPickerSchema = createSchema((value, context) => {
36613
- if (value === void 0) return { value: {
36614
- name: "bud:dom",
36615
- module: null
36616
- } };
36617
- if (typeof value === "string" && builtIn.has(value)) return { value: {
36618
- name: value,
36619
- module: null
36620
- } };
36621
- let result = projectPicker["~run"](value, context);
36622
- return result.issues ? result : { value: {
36623
- name: result.value,
36624
- module: result.value
36625
- } };
36849
+ Struct({
36850
+ name: text.check(isPattern(/^[^\r\n<>]+$/)),
36851
+ email: text.check(isPattern(/^[^\r\n<>]+$/))
36852
+ });
36853
+ Struct({
36854
+ ref: ref$1,
36855
+ expectedHead: NullOr(oid),
36856
+ files: ArraySchema(Struct({
36857
+ path: path$1,
36858
+ contents: Union([
36859
+ String$1,
36860
+ Uint8Array$1,
36861
+ Null
36862
+ ]),
36863
+ mode: optional$1(mode)
36864
+ })).check(isMaxLength(1e4)),
36865
+ message: text,
36866
+ extraParents: optional$1(ArraySchema(oid).check(isMaxLength(16)))
36626
36867
  });
36627
- //#endregion
36628
- //#region ../project/src/lib/previews.ts
36629
- var readyMessage = "ready must be a CSS selector, { selector } or { expression }.";
36630
- /** A readiness condition; a string is a selector. */
36631
- var PreviewReadySchema = createSchema((value, context) => {
36632
- let ready = typeof value === "string" ? { selector: value } : value;
36633
- if (ready && typeof ready === "object" && !Array.isArray(ready)) {
36634
- let entries = Object.entries(ready);
36635
- let [key, text] = entries[0] ?? [];
36636
- if (entries.length === 1 && (key === "selector" || key === "expression") && typeof text === "string" && text.trim() && text.length <= 4096) return { value: key === "selector" ? { selector: text.trim() } : { expression: text.trim() } };
36637
- }
36638
- return fail(readyMessage, context.path);
36868
+ Struct({
36869
+ ...Struct({
36870
+ base: optional$1(oid),
36871
+ target: optional$1(oid),
36872
+ staged: optional$1(Boolean$1),
36873
+ paths: optional$1(ArraySchema(path$1).check(isMaxLength(1e4)))
36874
+ }).fields,
36875
+ context: optional$1(Int$1.check(isBetween({
36876
+ minimum: 0,
36877
+ maximum: 1e3
36878
+ })))
36639
36879
  });
36640
- var defaultThemes = () => ["light", "dark"];
36641
- var defaultViewports = () => [{
36642
- id: "desktop",
36643
- name: "Desktop",
36644
- width: 1440,
36645
- height: 900,
36646
- mobile: false
36647
- }];
36648
- var viewportsMessage = "previews.viewports must list up to 4 { name, width, height?, mobile? } with distinct names, widths from 240 to 3840 and heights from 240 to 4000.";
36649
- function viewportId(name) {
36650
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
36651
- }
36652
- var ViewportsSchema = createSchema((value, context) => {
36653
- if (!Array.isArray(value) || value.length === 0 || value.length > 4) return fail(viewportsMessage, context.path);
36654
- let viewports = [];
36655
- for (let entry of value) {
36656
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) return fail(viewportsMessage, context.path);
36657
- let { name, width, height, mobile, ...rest } = entry;
36658
- let size = (pixels, from, to) => typeof pixels === "number" && Number.isInteger(pixels) && pixels >= from && pixels <= to;
36659
- if (Object.keys(rest).length || typeof name !== "string" || !name.trim() || name.length > 40 || !viewportId(name) || !size(width, 240, 3840) || height !== void 0 && !size(height, 240, 4e3) || mobile !== void 0 && typeof mobile !== "boolean") return fail(viewportsMessage, context.path);
36660
- let phone = mobile ?? width < 800;
36661
- viewports.push({
36662
- id: viewportId(name),
36663
- name: name.trim(),
36664
- width,
36665
- height: height ?? (phone ? Math.round(width * 844 / 390) : 900),
36666
- mobile: phone
36667
- });
36668
- }
36669
- if (new Set(viewports.map((viewport) => viewport.id)).size !== viewports.length) return fail(viewportsMessage, context.path);
36670
- return { value: viewports };
36880
+ Struct({ environment: optional$1(Record(String$1, Union([String$1, Undefined]))) });
36881
+ Struct({
36882
+ commit: oid,
36883
+ path: path$1,
36884
+ limit: Int$1.check(isBetween({
36885
+ minimum: 1,
36886
+ maximum: 101
36887
+ })),
36888
+ skip: optional$1(Int$1.check(isBetween({
36889
+ minimum: 0,
36890
+ maximum: 1e4
36891
+ })))
36671
36892
  });
36672
- /** The preview settings, with their defaults filled in. */
36673
- var BudPreviewsSchema = defaulted(settings("previews", {
36674
- scale: fallback(described(number().refine((scale) => Number.isInteger(scale) && scale >= 1 && scale <= 3), "previews.scale must be 1, 2 or 3."), () => 2),
36675
- themes: fallback(described(array(enum_(["light", "dark"])).refine((themes) => themes.length > 0 && new Set(themes).size === themes.length), "previews.themes must list light, dark or both, once each."), defaultThemes),
36676
- viewports: fallback(ViewportsSchema, defaultViewports),
36677
- ready: fallback(PreviewReadySchema, () => null),
36678
- readyTimeout: fallback(described(number().refine((ms) => Number.isInteger(ms) && ms >= 1e3 && ms <= 6e5), "previews.readyTimeout must be whole milliseconds from 1000 to 600000."), () => 6e4),
36679
- css: fallback(described(string().refine((css) => css.length <= 2e4 && !css.includes("<")), "previews.css must be CSS text without <, up to 20000 characters."), () => "")
36680
- }), () => ({
36681
- scale: 2,
36682
- themes: defaultThemes(),
36683
- viewports: defaultViewports(),
36684
- ready: null,
36685
- readyTimeout: 6e4,
36686
- css: ""
36893
+ Struct({
36894
+ from: revision,
36895
+ limit: Int$1.check(isBetween({
36896
+ minimum: 1,
36897
+ maximum: 500
36898
+ }))
36899
+ });
36900
+ Int$1.check(isBetween({
36901
+ minimum: 1,
36902
+ maximum: 1e3
36687
36903
  }));
36688
36904
  //#endregion
36689
- //#region ../project/src/lib/screen-routes.ts
36905
+ //#region ../git/src/lib/git.ts
36906
+ /** Opens the Git repository that contains a directory. */
36907
+ var Git = class extends Service()("@bud.sh/git/Git") {};
36908
+ //#endregion
36909
+ //#region ../live/src/server/hub.ts
36690
36910
  /**
36691
- * Validates a screen's route map without importing its component module.
36692
- * @param routes - A Remix route map shared with the screen's definition.
36693
- * @param path - The configured screen's static path.
36694
- * @returns Named patterns confined to the screen's path.
36911
+ * Orders every published change into one stream per companion. Clients resume from their
36912
+ * last event id; anything older than the replay buffer, or from an earlier companion,
36913
+ * receives a resync instead.
36695
36914
  */
36696
- function resolveScreenRoutes(routes, path) {
36697
- let result = [];
36698
- let seen = /* @__PURE__ */ new Set();
36699
- function visit(value, prefix) {
36700
- if (!value || typeof value !== "object" || Array.isArray(value) || seen.has(value)) throw new Error("Screen routes must be a Remix route map.");
36701
- seen.add(value);
36702
- for (let [key, entry] of Object.entries(value)) {
36703
- let name = prefix ? `${prefix}.${key}` : key;
36704
- if (!entry || typeof entry !== "object") throw new Error(`Screen route ${name} must be a Remix route.`);
36705
- if (!("pattern" in entry)) {
36706
- visit(entry, name);
36707
- continue;
36708
- }
36709
- if (!["GET", "ANY"].includes(entry.method) || typeof entry.href !== "function" || typeof entry.pattern?.source !== "string") throw new Error(`Screen route ${name} must be a GET route.`);
36710
- let pattern = RoutePattern.parse(entry.pattern.source);
36711
- let parts = pattern.toJSON();
36712
- let pathname = `/${parts.pathname}`;
36713
- if (parts.protocol || parts.hostname || parts.port || parts.pathname.split("/").some((segment) => /^\.{1,2}$/.test(decodeURIComponent(segment))) || pathname !== path && !pathname.startsWith(`${path}/`) && !pathname.startsWith(`${path}(/`)) throw new Error(`Screen route ${name} must stay under ${path}.`);
36714
- result.push({
36715
- name,
36716
- pattern: pattern.source
36717
- });
36718
- }
36719
- seen.delete(value);
36915
+ var LiveHub = class {
36916
+ epoch = randomBytes(8).toString("hex");
36917
+ #sequence = 0;
36918
+ #buffer = [];
36919
+ #replay;
36920
+ #heartbeatMs;
36921
+ #listeners = /* @__PURE__ */ new Set();
36922
+ constructor(options = {}) {
36923
+ this.#replay = options.replay ?? 1024;
36924
+ this.#heartbeatMs = options.heartbeatMs ?? 15e3;
36720
36925
  }
36721
- visit(routes, "");
36722
- if (!result.length) throw new Error("Screen routes must contain at least one route.");
36723
- return result;
36724
- }
36926
+ /**
36927
+ * Sends a change to every connected client.
36928
+ * @param topic - The domain, such as `previews` or `files`.
36929
+ * @param data - JSON data each domain validates with its own schema.
36930
+ */
36931
+ publish(topic, data) {
36932
+ let event = {
36933
+ id: `${this.epoch}:${++this.#sequence}`,
36934
+ topic,
36935
+ data
36936
+ };
36937
+ this.#buffer.push(event);
36938
+ if (this.#buffer.length > this.#replay) this.#buffer.shift();
36939
+ for (let listener of [...this.#listeners]) listener(event);
36940
+ }
36941
+ /**
36942
+ * Opens one client's `text/event-stream`.
36943
+ * @param lastEventId - The client's `Last-Event-ID`, if it is resuming.
36944
+ * @param signal - The request's lifetime; aborting closes the stream.
36945
+ * @returns The response body.
36946
+ */
36947
+ open(lastEventId, signal) {
36948
+ let encoder = new TextEncoder();
36949
+ let close = () => {};
36950
+ return new ReadableStream({
36951
+ start: (controller) => {
36952
+ let send = (text) => controller.enqueue(encoder.encode(text));
36953
+ let write = (event) => send(`id: ${event.id}\nevent: ${event.topic}\ndata: ${JSON.stringify(event.data)}\n\n`);
36954
+ let missed = this.#since(lastEventId);
36955
+ if (missed) for (let event of missed) write(event);
36956
+ else write({
36957
+ id: `${this.epoch}:${this.#sequence}`,
36958
+ topic: RESYNC,
36959
+ data: null
36960
+ });
36961
+ this.#listeners.add(write);
36962
+ let heartbeat = setInterval(() => send(": heartbeat\n\n"), this.#heartbeatMs);
36963
+ close = () => {
36964
+ clearInterval(heartbeat);
36965
+ this.#listeners.delete(write);
36966
+ try {
36967
+ controller.close();
36968
+ } catch {}
36969
+ };
36970
+ if (signal.aborted) close();
36971
+ else signal.addEventListener("abort", close, { once: true });
36972
+ },
36973
+ cancel: () => close()
36974
+ });
36975
+ }
36976
+ #since(lastEventId) {
36977
+ let last = lastEventId ? parseLiveId(lastEventId) : null;
36978
+ if (!last || last.epoch !== this.epoch || last.sequence > this.#sequence) return null;
36979
+ let oldest = this.#buffer[0] ? parseLiveId(this.#buffer[0].id).sequence : this.#sequence + 1;
36980
+ if (last.sequence < oldest - 1) return null;
36981
+ return this.#buffer.filter((event) => parseLiveId(event.id).sequence > last.sequence);
36982
+ }
36983
+ };
36725
36984
  //#endregion
36726
- //#region ../project/src/lib/screens.ts
36727
- /**
36728
- * Bud's browser of your live pages, with their comments.
36729
- * @returns The built-in screen, ready to relabel by spreading.
36730
- */
36731
- function pages() {
36732
- return {
36733
- id: "pages",
36734
- label: "Pages",
36735
- icon: "browser",
36736
- path: "/pages",
36737
- module: "bud:pages"
36738
- };
36739
- }
36740
- /**
36741
- * Bud's canvas of every route's screenshot.
36742
- * @returns The built-in screen, ready to relabel by spreading.
36743
- */
36744
- function canvas() {
36745
- return {
36746
- id: "canvas",
36747
- label: "Canvas",
36748
- icon: "grid",
36749
- path: "/canvas",
36750
- module: "bud:canvas"
36751
- };
36752
- }
36753
- /**
36754
- * Bud's Beads task board.
36755
- * @returns The built-in screen, ready to relabel by spreading.
36756
- */
36757
- function tasks() {
36758
- return {
36759
- id: "tasks",
36760
- label: "Tasks",
36761
- icon: "tasks",
36762
- path: "/tasks",
36763
- module: "bud:tasks"
36764
- };
36765
- }
36985
+ //#region ../live/src/server/service.ts
36986
+ /** The companion's ordered live channel: publishes changes and opens client streams. */
36987
+ var LiveChannel = class extends Service()("@bud.sh/live/LiveChannel") {};
36766
36988
  /**
36767
- * Bud's file browser and review.
36768
- * @returns The built-in screen, ready to relabel by spreading.
36989
+ * One ordered live channel per companion.
36990
+ * @param options - Replay buffer and heartbeat settings.
36991
+ * @returns The LiveChannel layer.
36769
36992
  */
36770
- function files() {
36771
- return {
36772
- id: "files",
36773
- label: "Files",
36774
- icon: "files",
36775
- path: "/files",
36776
- module: "bud:files"
36777
- };
36778
- }
36779
- /** The screens shown when the config doesn't list any. */
36780
- var defaultScreens = [
36781
- pages(),
36782
- canvas(),
36783
- tasks(),
36784
- files()
36785
- ];
36786
- var builtIns$1 = new Map(defaultScreens.map((screen) => [screen.module, screen]));
36787
- /** First path segments owned by Bud's routes, its development server and its public assets. */
36788
- var reservedScreenPaths = [
36789
- "/comments",
36790
- ...defaultScreens.map((screen) => screen.path),
36791
- "/api",
36792
- "/health",
36793
- "/__bud",
36794
- "/assets",
36795
- "/app",
36796
- "/src",
36797
- "/node_modules",
36798
- "/agents",
36799
- "/brand",
36800
- "/fonts",
36801
- "/frameworks",
36802
- "/licenses"
36803
- ];
36804
- var ScreenSchema = check(described(any().refine((value) => !!value && typeof value === "object"), "Each screen must be an object."), ({ id, label, icon = "layers", module, path }) => {
36805
- if (typeof id !== "string" || !/^[a-z][a-z0-9-]{0,31}$/.test(id)) return "Screen ids are 1-32 lowercase letters, digits and hyphens.";
36806
- if (typeof label !== "string" || !label.trim()) return `Screen ${id} needs a label.`;
36807
- if (typeof icon !== "string" || !icon) return `Screen ${id} needs an icon name.`;
36808
- if (typeof module !== "string") return `Screen ${id} needs a module.`;
36809
- let builtIn = builtIns$1.get(module);
36810
- path ??= builtIn?.path ?? `/${id}`;
36811
- if (builtIn) {
36812
- if (id !== builtIn.id || path !== builtIn.path) return `${module} keeps id "${builtIn.id}" and path "${builtIn.path}".`;
36813
- return;
36814
- }
36815
- if (!/^\.\/[^\0\\]+\.(?:ts|tsx|js|jsx)$/.test(module)) return `Screen ${id} must be a ./ module ending in .ts, .tsx, .js or .jsx.`;
36816
- if (typeof path !== "string" || !/^\/[a-z0-9][a-z0-9-]*$/.test(path)) return `Screen ${id} needs one static path segment, such as /${id}.`;
36817
- if (defaultScreens.some((screen) => screen.id === id)) return `Screen id "${id}" is reserved for a built-in screen.`;
36818
- if (reservedScreenPaths.includes(path)) return `Bud reserves the path ${path}.`;
36819
- });
36820
- /** Bud's navigation in order; file and icon checks need the app and happen at startup. */
36821
- var BudScreensSchema = defaulted(delegate(described(array(any()).refine((screens) => screens.length > 0), "screens must list at least one screen."), (screens) => {
36822
- let ids = /* @__PURE__ */ new Set();
36823
- let paths = /* @__PURE__ */ new Set();
36824
- return screens.map((value) => {
36825
- let input = parseConfig(ScreenSchema, value);
36826
- let { id, label, icon = "layers", module } = input;
36827
- let path = input.path ?? builtIns$1.get(module)?.path ?? `/${id}`;
36828
- if (ids.has(id) || paths.has(path)) throw new Error("Screen ids and paths must be unique.");
36829
- ids.add(id);
36830
- paths.add(path);
36831
- if (input.routes !== void 0) resolveScreenRoutes(input.routes, path);
36993
+ function liveChannelLayer(options = {}) {
36994
+ return sync$1(LiveChannel, () => {
36995
+ let hub = new LiveHub(options);
36832
36996
  return {
36833
- id,
36834
- label,
36835
- icon,
36836
- path,
36837
- module,
36838
- ...input.routes === void 0 ? {} : { routes: input.routes }
36997
+ epoch: hub.epoch,
36998
+ publish: (topic, data) => sync(() => hub.publish(topic, data)),
36999
+ open: (lastEventId, signal) => hub.open(lastEventId, signal)
36839
37000
  };
36840
37001
  });
36841
- }), () => defaultScreens.map((screen) => ({ ...screen })));
36842
- //#endregion
36843
- //#region ../project/src/lib/config.ts
36844
- var objectLike = (value) => !!value && typeof value === "object";
36845
- function section(message, shape) {
36846
- let schema = object(shape);
36847
- return createSchema((value, context) => objectLike(value) ? schema["~run"](Array.isArray(value) ? { ...value } : value, context) : fail(message, context.path));
36848
- }
36849
- var ConfigFields = check(section("Bud config must export an object.", {
36850
- routes: described(array(any()).refine((routes) => routes.length > 0), "bud.config must define at least one route."),
36851
- name: optional(described(string().refine((name) => !!name.trim()), "name must be a nonempty string.")),
36852
- target: optional(section("target must be an object.", {
36853
- origin: any(),
36854
- port: any()
36855
- })),
36856
- comments: optional(section("comments must be an object.", { remote: any() })),
36857
- port: any(),
36858
- screens: any(),
36859
- storage: any(),
36860
- designs: any(),
36861
- previews: any(),
36862
- files: any(),
36863
- logo: any(),
36864
- picker: any(),
36865
- branches: any(),
36866
- flows: any()
36867
- }), ({ target }) => {
36868
- if (target?.port === 0) return "target.port must be a listening port, not zero.";
36869
- if (target?.origin !== void 0 && (typeof target.origin !== "string" || !target.origin.trim())) return "target.origin must be an HTTP(S) origin.";
36870
- });
36871
- var RouteSchema = check(any().transform((input) => typeof input === "string" ? {
36872
- path: input,
36873
- title: input === "/" ? "Home" : input
36874
- } : input), (route) => {
36875
- if (!route || typeof route.path !== "string" || !route.path.startsWith("/") || route.path.startsWith("//")) return "Canvas routes must be local paths beginning with a single /.";
36876
- if (new URL(route.path, "http://bud.invalid").origin !== "http://bud.invalid" || route.path.includes("\\")) return "Canvas routes must stay on the target origin.";
36877
- let id = route.id ?? routePath(route.path);
36878
- if (typeof id !== "string" || !id.trim()) return "Canvas route IDs and paths must be unique.";
36879
- if (typeof route.title !== "string" || !route.title.trim()) return "Each route needs a title.";
36880
- if (route.pattern !== void 0 && (typeof route.pattern !== "string" || !route.pattern.startsWith("/"))) return "A route pattern must be a path beginning with /.";
36881
- if (route.group !== void 0 && (typeof route.group !== "string" || !route.group.trim() || route.group.length > 120)) return "A route group must be a short name.";
36882
- if (route.ready !== void 0 && route.ready !== false && !parseSafe(PreviewReadySchema, route.ready).success) return "A route ready must be false, a CSS selector, { selector } or { expression }.";
36883
- }).transform((route) => {
36884
- let path = routePath(route.path);
36885
- return {
36886
- id: route.id ?? path,
36887
- path,
36888
- title: route.title,
36889
- ...route.pattern === void 0 ? {} : { pattern: route.pattern },
36890
- ...route.group === void 0 ? {} : { group: route.group.trim() },
36891
- ...route.ready === void 0 ? {} : { ready: route.ready === false ? null : parseConfig(PreviewReadySchema, route.ready) }
36892
- };
36893
- });
36894
- function routePath(path) {
36895
- let url = new URL(path, "http://bud.invalid");
36896
- let search = url.searchParams.toString();
36897
- return `${url.pathname}${search ? `?${search}` : ""}${url.hash}`;
36898
37002
  }
36899
- /** Canvas routes with unique IDs and normalized paths. */
36900
- var BudRoutesSchema = check(list(RouteSchema, "bud.config must define at least one route.", 1), (routes) => new Set(routes.map((route) => route.id)).size !== routes.length || new Set(routes.map((route) => route.path)).size !== routes.length ? "Canvas route IDs and paths must be unique." : void 0);
36901
- var port = (value) => value === void 0 || Number.isInteger(value) && value >= 0 && value <= 65535;
36902
- var remote = (value) => typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value);
36903
- /** A project's config, validated and normalized before Bud starts any listeners. */
36904
- var BudConfigSchema = delegate(check(delegate(check(delegate(check(delegate(ConfigFields, (config) => ({
36905
- ...config,
36906
- routes: parseConfig(BudRoutesSchema, config.routes)
36907
- })), ({ port: companion, target }) => port(companion ?? 0) && port(target?.port) ? void 0 : "Ports must be integers between 0 and 65535."), ({ target, ...config }) => {
36908
- let origin = target?.origin ?? (target?.port ? `http://127.0.0.1:${target.port}` : void 0);
36909
- return {
36910
- ...config,
36911
- targetOrigin: origin ? normalizeOrigin(origin, "target.origin must be an HTTP(S) origin without a path or credentials.") : origin
36912
- };
36913
- }), ({ comments }) => remote(comments?.remote ?? "origin") ? void 0 : "comments.remote must be a Git remote name."), (config) => {
36914
- let flows = parseConfig(BudFlowsSchema, config.flows);
36915
- let steps = flows.flatMap((flow) => flow.steps.map((step) => flowStepRoute(flow, step)));
36916
- let home = config.routes.findIndex((route) => route.path === "/") + 1;
36917
- return {
36918
- ...config,
36919
- flows,
36920
- routes: [
36921
- ...config.routes.slice(0, home),
36922
- ...steps,
36923
- ...config.routes.slice(home)
36924
- ]
36925
- };
36926
- }), ({ routes }) => new Set(routes.map((route) => route.id)).size !== routes.length || new Set(routes.map((route) => route.path)).size !== routes.length ? "A flow step must not reuse a route’s id or path." : void 0), (config) => ({
36927
- name: config.name ?? "Your project",
36928
- routes: config.routes,
36929
- flows: config.flows,
36930
- port: config.port ?? 0,
36931
- targetOrigin: config.targetOrigin,
36932
- remote: config.comments?.remote ?? "origin",
36933
- screens: parseConfig(BudScreensSchema, config.screens),
36934
- storage: parseConfig(BudStorageSchema, config.storage),
36935
- designs: parseConfig(BudDesignsSchema, config.designs),
36936
- previews: parseConfig(BudPreviewsSchema, config.previews),
36937
- files: parseConfig(BudFilesSchema, config.files),
36938
- logo: parseConfig(BudLogoSchema, config.logo),
36939
- picker: parseConfig(BudPickerSchema, config.picker),
36940
- branches: parseConfig(BudBranchesSchema, config.branches)
36941
- }));
36942
37003
  /**
36943
- * Validates executable project config before starting any listeners.
36944
- * @param value - The config module's default export.
36945
- * @returns Normalized route IDs, ports, and URLs.
37004
+ * Publishes every element of a domain's change stream on the live channel, for the lifetime
37005
+ * of the layer that forks it.
37006
+ * @param topic - The topic clients subscribe to.
37007
+ * @param changes - The domain's change stream.
37008
+ * @returns An effect to fork in a scoped layer.
36946
37009
  */
36947
- function resolveBudConfig(value) {
36948
- return parseConfig(BudConfigSchema, value);
37010
+ function publishChanges(topic, changes) {
37011
+ return gen(function* () {
37012
+ let channel = yield* LiveChannel;
37013
+ yield* changes.pipe(runForEach((change) => channel.publish(topic, change)), ignore$2, forkScoped);
37014
+ });
36949
37015
  }
37016
+ //#endregion
37017
+ //#region ../project/src/server/errors.ts
37018
+ /** Expected project startup/configuration failure; nested causes remain server-only. */
37019
+ var ProjectError = class extends TaggedError("ProjectError") {};
37020
+ //#endregion
37021
+ //#region ../project/src/server/modules.ts
36950
37022
  /**
36951
- * Returns a bare HTTP(S) origin, rejecting credentials, paths, queries and fragments.
36952
- * @param origin - The URL to check.
36953
- * @param message - The error's message when it is not a bare origin.
36954
- * @returns The URL's origin.
37023
+ * Confines a validated config module to a regular, bounded file in its project.
37024
+ * @param root - The trusted consumer directory.
37025
+ * @param module - A project-relative module already validated by the config resolver.
37026
+ * @param options - The config setting and whether its source runs on the server or browser.
37027
+ * @returns The canonical filename, without evaluating its code.
36955
37028
  */
36956
- function normalizeOrigin(origin, message) {
36957
- let url = new URL(origin);
36958
- if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.pathname !== "/" || url.search || url.hash) throw new Error(message);
36959
- return url.origin;
37029
+ function projectModuleFile(root, module, options) {
37030
+ return gen(function* () {
37031
+ let fs = yield* FileSystem;
37032
+ let path = yield* Path$1;
37033
+ let { setting, kind } = options;
37034
+ let project = yield* fs.realPath(root).pipe(mapError$2((cause) => new ProjectError({
37035
+ reason: "root",
37036
+ message: "Cannot open the module project root.",
37037
+ cause
37038
+ })));
37039
+ let file = yield* fs.realPath(path.resolve(project, module)).pipe(mapError$2((cause) => new ProjectError({
37040
+ reason: "read",
37041
+ message: `${setting} cannot find ${module} in the project.`,
37042
+ cause
37043
+ })));
37044
+ let relative = path.relative(project, file);
37045
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) || relative.split(path.sep).includes("node_modules")) return yield* new ProjectError({
37046
+ reason: "invalid",
37047
+ message: `${setting} must resolve to a project-owned file outside node_modules.`
37048
+ });
37049
+ if (kind === "server" && !file.endsWith(".server.ts")) return yield* new ProjectError({
37050
+ reason: "invalid",
37051
+ message: `${setting} must resolve to a .server.ts module.`
37052
+ });
37053
+ if (kind === "browser" && (!/\.(?:ts|tsx|js|jsx)$/.test(file) || /\.server\.[cm]?[jt]sx?$/.test(file))) return yield* new ProjectError({
37054
+ reason: "invalid",
37055
+ message: `${setting} must resolve to a browser module.`
37056
+ });
37057
+ if ((yield* fs.stat(file).pipe(mapError$2((cause) => new ProjectError({
37058
+ reason: "read",
37059
+ message: `${setting} cannot inspect ${module}.`,
37060
+ cause
37061
+ })))).type !== "File") return yield* new ProjectError({
37062
+ reason: "invalid",
37063
+ message: `${setting} must be a regular module file.`
37064
+ });
37065
+ yield* readText(file, { maxBytes: 262144 }).pipe(mapError$2((cause) => new ProjectError({
37066
+ reason: "read",
37067
+ message: `${setting} must be a readable module no larger than 256 KiB.`,
37068
+ cause
37069
+ })));
37070
+ return file;
37071
+ });
36960
37072
  }
36961
37073
  //#endregion
36962
37074
  //#region ../project/src/server/config.ts
@@ -37361,10 +37473,10 @@ function ProjectLiveStatus(handle) {
37361
37473
  //#endregion
37362
37474
  //#region app/client/screens.ts
37363
37475
  var builtIns = {
37364
- "bud:pages": () => import("./assets/pages-0aWH7z1j.js"),
37365
- "bud:canvas": () => import("./assets/canvas-BZhzizzt.js"),
37366
- "bud:tasks": () => import("./assets/tasks-C0WqnM7Q.js"),
37367
- "bud:files": () => import("./assets/files-CTzcWGjb.js")
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")
37368
37480
  };
37369
37481
  /**
37370
37482
  * Imports a screen module: a built-in by its key, any other by the URL the server gave it.
@@ -37450,7 +37562,7 @@ function ScreenHost(handle) {
37450
37562
  } catch (cause) {
37451
37563
  if (handle.signal.aborted) return;
37452
37564
  console.error(cause);
37453
- error = cause instanceof Error && cause.message ? cause.message : `Could not open ${label}.`;
37565
+ error = cause instanceof Error && cause.message ? cause.message : t("app.client.screenHost.couldNotOpen", { label });
37454
37566
  }
37455
37567
  handle.update();
37456
37568
  }
@@ -37462,7 +37574,7 @@ function ScreenHost(handle) {
37462
37574
  let context = opened?.context;
37463
37575
  let routeError = "";
37464
37576
  if (opened && context) try {
37465
- if (location.routes && !matchScreenRoutes(location.routes, location.href)) throw new Error(`No screen route matches ${location.href}.`);
37577
+ if (location.routes && !matchScreenRoutes(location.routes, location.href)) throw new Error(t("app.client.screenHost.noRouteMatches", { href: location.href }));
37466
37578
  context = matchScreenContext(opened.screen.routes, {
37467
37579
  ...context,
37468
37580
  active: handle.props.active,
@@ -37470,7 +37582,7 @@ function ScreenHost(handle) {
37470
37582
  url: new URL(location.href, context.url.origin)
37471
37583
  });
37472
37584
  } catch (cause) {
37473
- routeError = cause instanceof Error ? cause.message : "This screen route is invalid.";
37585
+ routeError = cause instanceof Error ? cause.message : t("app.client.screenHost.invalidRoute");
37474
37586
  }
37475
37587
  return /* @__PURE__ */ jsx("div", {
37476
37588
  class: "h-full min-h-0",
@@ -37487,7 +37599,7 @@ function ScreenHost(handle) {
37487
37599
  class: "max-w-2xl",
37488
37600
  role: "alert",
37489
37601
  children: [/* @__PURE__ */ jsx(FieldCardHeader, {
37490
- title: `${label} didn't open`,
37602
+ title: t("app.client.screenHost.didntOpen", { label }),
37491
37603
  subtitle: error
37492
37604
  }), /* @__PURE__ */ jsxs(FieldCardContent, {
37493
37605
  class: "flex-col items-start gap-3",
@@ -37495,17 +37607,17 @@ function ScreenHost(handle) {
37495
37607
  as: "p",
37496
37608
  variant: "ui",
37497
37609
  tone: "muted",
37498
- children: "Fix the screen and retry. Details are in the browser console."
37610
+ children: t("app.client.screenHost.fixAndRetry")
37499
37611
  }), /* @__PURE__ */ jsx(Button, {
37500
37612
  mix: on("click", () => {
37501
37613
  if ("error" in module || handle.props.taskDesign && "error" in handle.props.taskDesign) window.location.reload();
37502
37614
  else open();
37503
37615
  }),
37504
- children: "Retry"
37616
+ children: t("app.client.screenHost.retry")
37505
37617
  })]
37506
37618
  })]
37507
37619
  })
37508
- }) : /* @__PURE__ */ jsx(Loader, { label: `Loading ${label}…` })
37620
+ }) : /* @__PURE__ */ jsx(Loader, { label: t("app.client.screenHost.loading", { label }) })
37509
37621
  });
37510
37622
  };
37511
37623
  }
@@ -37524,11 +37636,11 @@ async function resolveConversation(id, targets, token, signal, fetcher = fetch)
37524
37636
  id = parseThreadId(id);
37525
37637
  let thread = await requestComments(routes.comments.get.href({ id }), token, signal, parseThread, fetcher);
37526
37638
  signal.throwIfAborted();
37527
- if (thread.id !== id) throw new Error("The comments service returned another discussion.");
37639
+ if (thread.id !== id) throw new Error(t("app.client.conversationRoute.anotherDiscussion"));
37528
37640
  let destination = conversationDestination(thread);
37529
37641
  let target = targets.find((target) => target.module === destination.module);
37530
- if (!target) throw new Error("Enable the screen that owns this discussion to open it.");
37531
- if (target.routes && !matchScreenRoutes(target.routes, destination.href)) throw new Error("The discussion location is not available in this screen.");
37642
+ if (!target) throw new Error(t("app.client.conversationRoute.enableOwningScreen"));
37643
+ if (target.routes && !matchScreenRoutes(target.routes, destination.href)) throw new Error(t("app.client.conversationRoute.locationUnavailable"));
37532
37644
  return {
37533
37645
  active: target.id,
37534
37646
  module: { url: target.module },
@@ -37603,14 +37715,14 @@ function Workspace(handle) {
37603
37715
  changed();
37604
37716
  try {
37605
37717
  let project = handle.props.project;
37606
- if (!project) throw new Error("Connect a project to open this discussion.");
37718
+ if (!project) throw new Error(t("app.client.workspace.connectProjectToOpenDiscussion"));
37607
37719
  let next = await resolveConversation(id, handle.props.conversationTargets ?? [], project.token, signal);
37608
37720
  if (!signal.aborted && conversation === id) {
37609
37721
  presented = next;
37610
37722
  hrefs.set(next.active, window.location.pathname);
37611
37723
  }
37612
37724
  } catch (cause) {
37613
- if (!signal.aborted) error = cause instanceof Error ? cause.message : "Could not open this discussion.";
37725
+ if (!signal.aborted) error = cause instanceof Error ? cause.message : t("app.client.workspace.couldNotOpenDiscussion");
37614
37726
  } finally {
37615
37727
  if (request === controller && !signal.aborted) {
37616
37728
  loading = false;
@@ -37653,7 +37765,7 @@ function Workspace(handle) {
37653
37765
  let { pathname } = new URL(href, "http://bud.local");
37654
37766
  if (readConversationLocation(href)) return {
37655
37767
  href,
37656
- title: "Discussion",
37768
+ title: t("app.client.workspace.discussion"),
37657
37769
  icon: "message"
37658
37770
  };
37659
37771
  let match = handle.props.screens.map((item) => ({
@@ -37815,19 +37927,19 @@ function Workspace(handle) {
37815
37927
  children: project ? /* @__PURE__ */ jsxs("div", {
37816
37928
  class: "h-full min-h-0",
37817
37929
  mix: mount,
37818
- children: [loading ? /* @__PURE__ */ jsx(Loader, { label: "Opening discussion…" }) : error ? /* @__PURE__ */ jsx("div", {
37930
+ children: [loading ? /* @__PURE__ */ jsx(Loader, { label: t("app.client.workspace.openingDiscussion") }) : error ? /* @__PURE__ */ jsx("div", {
37819
37931
  class: "p-3 md:p-4",
37820
37932
  children: /* @__PURE__ */ jsxs(FieldCard, {
37821
37933
  class: "max-w-2xl",
37822
37934
  role: "alert",
37823
37935
  children: [/* @__PURE__ */ jsx(FieldCardHeader, {
37824
- title: "Discussion didn't open",
37936
+ title: t("app.client.workspace.discussionDidntOpen"),
37825
37937
  subtitle: error
37826
37938
  }), /* @__PURE__ */ jsx(FieldCardContent, { children: /* @__PURE__ */ jsx(Button, {
37827
37939
  mix: on("click", () => {
37828
37940
  if (conversation) resolve(conversation);
37829
37941
  }),
37830
- children: "Retry"
37942
+ children: t("app.client.workspace.retry")
37831
37943
  }) })]
37832
37944
  })
37833
37945
  }) : null, [...opened].map(([id, { module, taskDesign, location }]) => /* @__PURE__ */ jsx("div", {
@@ -37845,12 +37957,12 @@ function Workspace(handle) {
37845
37957
  }) : /* @__PURE__ */ jsxs(FieldCard, {
37846
37958
  class: "max-w-2xl",
37847
37959
  children: [/* @__PURE__ */ jsx(FieldCardHeader, {
37848
- title: "Your project, side by side.",
37849
- subtitle: "Open Bud beside a local project to review its pages, files and tasks."
37960
+ title: t("app.client.workspace.yourProjectSideBySide"),
37961
+ subtitle: t("app.client.workspace.openBudBesideLocalProject")
37850
37962
  }), /* @__PURE__ */ jsx(FieldCardContent, { children: /* @__PURE__ */ jsx(Text, {
37851
37963
  as: "p",
37852
37964
  variant: "ui",
37853
- children: "Start your project with the Bud Vite plugin, or run Bud from its directory. Its screens appear here when the companion connects."
37965
+ children: t("app.client.workspace.startProjectWithBudVitePlugin")
37854
37966
  }) })]
37855
37967
  })
37856
37968
  });
@@ -38007,7 +38119,7 @@ function createAppController(template, project, runtime) {
38007
38119
  let title = (conversation ? "Discussion" : screen.label).replace(/[&<>]/g, (character) => `&#${character.charCodeAt(0)};`);
38008
38120
  let logo = project?.config.logo;
38009
38121
  let shell = logo ? withLogoIcons(template, logo, (theme) => routes.logo.href({ theme })) : template;
38010
- return new Response(shell.replace("<title>bud.sh</title>", () => `<title>${title} · bud.sh</title>`).replace("<!--app-html-->", () => content), { headers: { "Content-Type": "text/html; charset=utf-8" } });
38122
+ 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" } });
38011
38123
  }
38012
38124
  return createController(routes, { actions: {
38013
38125
  conversation({ params, request }) {
@@ -41139,6 +41251,19 @@ function previewAnchors(root) {
41139
41251
  };
41140
41252
  }
41141
41253
  //#endregion
41254
+ //#region app/server/locale.ts
41255
+ var requestLocale = new AsyncLocalStorage();
41256
+ useLocaleSource(() => requestLocale.getStore());
41257
+ /**
41258
+ * Runs a request's handling in its language, so what it renders speaks it.
41259
+ * @param value - The request's locale; English when it isn't one of Bud's.
41260
+ * @param handle - The request's handling.
41261
+ * @returns What the handling returns.
41262
+ */
41263
+ function inLocale(value, handle) {
41264
+ return requestLocale.run(isLocale(value) ? value : "en", handle);
41265
+ }
41266
+ //#endregion
41142
41267
  //#region app/server/hosted/contract.ts
41143
41268
  /**
41144
41269
  * The screens a hosted Bud shows: Pages as captured pictures, and no Files, which need a
@@ -41294,7 +41419,10 @@ function createHostedApp(options) {
41294
41419
  return made;
41295
41420
  };
41296
41421
  return {
41297
- fetch: async (request, context) => routerFor(hostedAuthor(context?.author)).fetch(request),
41422
+ fetch: async (request, context) => {
41423
+ let router = routerFor(hostedAuthor(context?.author));
41424
+ return inLocale(context?.locale ?? config.locale, () => router.fetch(request));
41425
+ },
41298
41426
  dispose: () => runtime.dispose()
41299
41427
  };
41300
41428
  }
@@ -41323,6 +41451,13 @@ async function createNodeHost(options) {
41323
41451
  return response;
41324
41452
  };
41325
41453
  let { config } = await (await call("project")).json();
41454
+ if (options.theme) config = {
41455
+ ...config,
41456
+ theme: resolveBudConfig({
41457
+ routes: ["/"],
41458
+ theme: options.theme
41459
+ }).theme
41460
+ };
41326
41461
  let client = options.client ?? fileURLToPath(new URL("../client/", import.meta.url));
41327
41462
  let template = await readFile(join(client, "index.html"), "utf8");
41328
41463
  let pictures = httpPreviewStorage(remote("store"), {