wizards 0.4.7 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/README.md +45 -42
  2. package/apps/runtime/dist/agents/studio-assistant.js +8 -8
  3. package/apps/runtime/dist/app.js +7 -4
  4. package/apps/runtime/dist/assistant/primer.js +1 -1
  5. package/apps/runtime/dist/auth/index.js +2 -2
  6. package/apps/runtime/dist/authoring/guide.js +8 -8
  7. package/apps/runtime/dist/cli/account.js +31 -28
  8. package/apps/runtime/dist/cli/autostart.js +5 -3
  9. package/apps/runtime/dist/cli/clients.js +1 -1
  10. package/apps/runtime/dist/cli/connect.js +24 -24
  11. package/apps/runtime/dist/cli/features.js +6 -6
  12. package/apps/runtime/dist/cli/main.js +51 -39
  13. package/apps/runtime/dist/cli/mcp.js +5 -4
  14. package/apps/runtime/dist/cli/menu.js +32 -30
  15. package/apps/runtime/dist/cli/report.js +7 -9
  16. package/apps/runtime/dist/cli/setup.js +42 -38
  17. package/apps/runtime/dist/cli/start.js +16 -15
  18. package/apps/runtime/dist/cli/status.js +20 -20
  19. package/apps/runtime/dist/cli/ui.js +9 -2
  20. package/apps/runtime/dist/cli/update.js +6 -6
  21. package/apps/runtime/dist/cli/validate.js +1 -1
  22. package/apps/runtime/dist/connectors/external.js +3 -2
  23. package/apps/runtime/dist/connectors/index.js +5 -5
  24. package/apps/runtime/dist/connectors/plugin-connections.js +2 -2
  25. package/apps/runtime/dist/connectors/via-account.js +5 -5
  26. package/apps/runtime/dist/db/control-schema.js +14 -0
  27. package/apps/runtime/dist/db/migrations/control/0009_plugin_claim.sql +9 -0
  28. package/apps/runtime/dist/db/migrations/control/meta/0009_snapshot.json +561 -0
  29. package/apps/runtime/dist/db/migrations/control/meta/_journal.json +7 -0
  30. package/apps/runtime/dist/db/schema.js +9 -9
  31. package/apps/runtime/dist/docs/dev/index.md +16 -14
  32. package/apps/runtime/dist/docs/dev/plugins/connections.md +27 -24
  33. package/apps/runtime/dist/docs/dev/plugins/events.md +19 -17
  34. package/apps/runtime/dist/docs/dev/plugins/examples.md +13 -12
  35. package/apps/runtime/dist/docs/dev/plugins/index.md +29 -24
  36. package/apps/runtime/dist/docs/dev/plugins/layout.md +20 -19
  37. package/apps/runtime/dist/docs/dev/plugins/quick-start.md +16 -15
  38. package/apps/runtime/dist/docs/dev/plugins/reference.md +52 -40
  39. package/apps/runtime/dist/docs/dev/plugins/reloading.md +24 -23
  40. package/apps/runtime/dist/docs/dev/plugins/routes.md +26 -22
  41. package/apps/runtime/dist/docs/dev/plugins/runners.md +49 -44
  42. package/apps/runtime/dist/docs/dev/plugins/server.md +95 -43
  43. package/apps/runtime/dist/docs/dev/plugins/shipping.md +19 -16
  44. package/apps/runtime/dist/docs/dev/plugins/space.md +75 -69
  45. package/apps/runtime/dist/docs/dev/plugins/starters.md +22 -17
  46. package/apps/runtime/dist/docs/dev/plugins/studio.md +80 -37
  47. package/apps/runtime/dist/docs/dev/plugins/styling.md +44 -41
  48. package/apps/runtime/dist/docs/dev/plugins/tables.md +11 -11
  49. package/apps/runtime/dist/docs/dev/plugins/tools.md +26 -25
  50. package/apps/runtime/dist/docs/dev/wizard-format.md +35 -23
  51. package/apps/runtime/dist/docs/user/ai-apps.md +46 -43
  52. package/apps/runtime/dist/docs/user/assistant.md +41 -36
  53. package/apps/runtime/dist/docs/user/build-a-wizard.md +64 -39
  54. package/apps/runtime/dist/docs/user/command-line.md +47 -44
  55. package/apps/runtime/dist/docs/user/connectors.md +40 -35
  56. package/apps/runtime/dist/docs/user/create-account.md +27 -22
  57. package/apps/runtime/dist/docs/user/data.md +29 -29
  58. package/apps/runtime/dist/docs/user/first-start.md +26 -28
  59. package/apps/runtime/dist/docs/user/index.md +16 -17
  60. package/apps/runtime/dist/docs/user/install.md +28 -27
  61. package/apps/runtime/dist/docs/user/local-only.md +21 -17
  62. package/apps/runtime/dist/docs/user/models.md +92 -84
  63. package/apps/runtime/dist/docs/user/plugins.md +28 -23
  64. package/apps/runtime/dist/docs/user/run.md +51 -50
  65. package/apps/runtime/dist/docs/user/server.md +9 -9
  66. package/apps/runtime/dist/docs/user/space.md +87 -65
  67. package/apps/runtime/dist/docs/user/steps.md +73 -69
  68. package/apps/runtime/dist/docs/user/test-and-share.md +127 -105
  69. package/apps/runtime/dist/docs/user/troubleshooting.md +40 -38
  70. package/apps/runtime/dist/docs/user/wordpress.md +62 -54
  71. package/apps/runtime/dist/documents/parse.js +2 -2
  72. package/apps/runtime/dist/engenty/shims/web-ingest.js +17 -1
  73. package/apps/runtime/dist/engine/input.js +4 -4
  74. package/apps/runtime/dist/engine/push.js +2 -2
  75. package/apps/runtime/dist/engine/runner.js +8 -8
  76. package/apps/runtime/dist/engine/steps.js +4 -4
  77. package/apps/runtime/dist/engine/surface.js +1 -1
  78. package/apps/runtime/dist/film/step.js +3 -3
  79. package/apps/runtime/dist/harness/claude.js +1 -1
  80. package/apps/runtime/dist/harness/codex.js +1 -1
  81. package/apps/runtime/dist/harness/model.js +2 -2
  82. package/apps/runtime/dist/harness/terminal.js +3 -3
  83. package/apps/runtime/dist/mcp/handler.js +2 -2
  84. package/apps/runtime/dist/mcp/tools.js +9 -9
  85. package/apps/runtime/dist/models.js +15 -15
  86. package/apps/runtime/dist/plugins/claims.js +56 -0
  87. package/apps/runtime/dist/plugins/loader.js +2 -0
  88. package/apps/runtime/dist/plugins/public.js +2 -3
  89. package/apps/runtime/dist/plugins/runs.js +13 -8
  90. package/apps/runtime/dist/render/chromium.js +24 -11
  91. package/apps/runtime/dist/routes/api.js +16 -10
  92. package/apps/runtime/dist/routes/debug.js +1 -1
  93. package/apps/runtime/dist/routes/delivery.js +46 -0
  94. package/apps/runtime/dist/routes/lang.js +7 -0
  95. package/apps/runtime/dist/routes/marketplace.js +2 -1
  96. package/apps/runtime/dist/routes/plugin-public.js +2 -1
  97. package/apps/runtime/dist/routes/plugins.js +13 -14
  98. package/apps/runtime/dist/routes/runs.js +59 -49
  99. package/apps/runtime/dist/routes/studio.js +58 -28
  100. package/apps/runtime/dist/services/access.js +3 -3
  101. package/apps/runtime/dist/services/assistant-chats.js +1 -1
  102. package/apps/runtime/dist/services/brand.js +2 -2
  103. package/apps/runtime/dist/services/cloud.js +5 -4
  104. package/apps/runtime/dist/services/errors.js +2 -1
  105. package/apps/runtime/dist/services/knowledge-model.js +10 -10
  106. package/apps/runtime/dist/services/knowledge.js +19 -19
  107. package/apps/runtime/dist/services/marketplace.js +2 -2
  108. package/apps/runtime/dist/services/package-file.js +12 -9
  109. package/apps/runtime/dist/services/package.js +3 -3
  110. package/apps/runtime/dist/services/project-files.js +15 -17
  111. package/apps/runtime/dist/services/project-index.js +10 -10
  112. package/apps/runtime/dist/services/projects.js +13 -8
  113. package/apps/runtime/dist/services/relevance.js +3 -3
  114. package/apps/runtime/dist/services/runners.js +4 -3
  115. package/apps/runtime/dist/services/runs.js +3 -3
  116. package/apps/runtime/dist/services/site.js +10 -10
  117. package/apps/runtime/dist/services/space-categories.js +22 -22
  118. package/apps/runtime/dist/services/space-data.js +16 -16
  119. package/apps/runtime/dist/services/space-origin.js +4 -4
  120. package/apps/runtime/dist/services/spaces.js +10 -10
  121. package/apps/runtime/dist/services/wizards.js +5 -5
  122. package/apps/runtime/dist/tools/index.js +1 -1
  123. package/apps/runtime/dist/tools/net-guard.js +135 -24
  124. package/apps/runtime/dist/tools/pages.js +4 -4
  125. package/apps/runtime/dist/tools/plugin.js +1 -1
  126. package/apps/runtime/dist/tools/project.js +10 -10
  127. package/apps/runtime/dist/tools/store.js +1 -1
  128. package/apps/runtime/dist/widgets/step.js +1 -1
  129. package/apps/web/dist/assets/AboutDialog-DaYwk84X.js +1 -0
  130. package/apps/web/dist/assets/{ChangelogDialog-Cm25slr3.js → ChangelogDialog-CsYbEayb.js} +1 -1
  131. package/apps/web/dist/assets/{ChatView-BXtXWnqK.js → ChatView-BRD3fKZU.js} +1 -1
  132. package/apps/web/dist/assets/EditorPage-ocPlZD3E.js +3 -0
  133. package/apps/web/dist/assets/{FlowPreview-BsfjDVv8.js → FlowPreview-Bm7sv746.js} +1 -1
  134. package/apps/web/dist/assets/{Harness-B0hD5Pp7.js → Harness-DKE-YNA5.js} +1 -1
  135. package/apps/web/dist/assets/{NewWizardChannels-DMDp9Ao5.js → NewWizardChannels-CnWckqjO.js} +1 -1
  136. package/apps/web/dist/assets/{NewWizardPage-aOisxyk2.js → NewWizardPage-BXTpbHVM.js} +1 -1
  137. package/apps/web/dist/assets/{OnboardingPage-Czb0pOyy.js → OnboardingPage-3Rz5uOsa.js} +1 -1
  138. package/apps/web/dist/assets/{ProjectPage-DPCtb42K.js → ProjectPage-QdSJwm3k.js} +1 -1
  139. package/apps/web/dist/assets/{PublicRunner-CvyA-gkf.js → PublicRunner-DEpBer5g.js} +1 -1
  140. package/apps/web/dist/assets/ReadOnly-rr_RJD-a.js +1 -0
  141. package/apps/web/dist/assets/{RunDrawer-BCiPEz9g.js → RunDrawer-BsZt0tL9.js} +1 -1
  142. package/apps/web/dist/assets/{RunnerView-4z6DMzE6.js → RunnerView-BbRukEdE.js} +1 -1
  143. package/apps/web/dist/assets/SettingsPage-qkWsQIMN.js +1 -0
  144. package/apps/web/dist/assets/{SetupPage-YFoU_nXK.js → SetupPage-PzvPPLfM.js} +1 -1
  145. package/apps/web/dist/assets/{SharePage-BBHiJ34e.js → SharePage-7vdDWG1n.js} +1 -1
  146. package/apps/web/dist/assets/changelog-BFdAYD28.js +1 -0
  147. package/apps/web/dist/assets/{esm-hSq-jeOG.js → esm-g0kaGp8t.js} +1 -1
  148. package/apps/web/dist/assets/{index-CSeBGwy0.css → index-Bi2x2g7n.css} +1 -1
  149. package/apps/web/dist/assets/{index-DB_iwB35.js → index-BzPid1oQ.js} +4 -4
  150. package/apps/web/dist/assets/lock-BK9RYIft.js +1 -0
  151. package/apps/web/dist/assets/oss-credits-BJrR7BR1.js +1 -0
  152. package/apps/web/dist/assets/{phone-CeqyvwK-.js → phone-DtkY0NfU.js} +1 -1
  153. package/apps/web/dist/assets/{settings-menu-BK8WpcGv.js → settings-menu-7EJQRFOi.js} +1 -1
  154. package/apps/web/dist/assets/{ui-BbIh6SOJ.js → ui-B6S5hYPd.js} +3 -3
  155. package/apps/web/dist/assets/{upload-BQrAlHIG.js → upload-rYCAy7ox.js} +1 -1
  156. package/apps/web/dist/index.html +3 -3
  157. package/apps/web/dist/mcp/flow.html +4 -4
  158. package/bin/wizards.mjs +1 -1
  159. package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +33 -14
  160. package/node_modules/@engenty-wizards/plugin-sdk/dist/studio.d.ts +3 -3
  161. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +690 -690
  162. package/node_modules/@engenty-wizards/shared/dist/definition.js +1 -1
  163. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.d.ts +76 -76
  164. package/node_modules/@engenty-wizards/shared/dist/feature-flags.d.ts +3 -3
  165. package/node_modules/@engenty-wizards/shared/dist/feature-flags.js +3 -3
  166. package/node_modules/@engenty-wizards/shared/dist/knowledge.d.ts +13 -13
  167. package/node_modules/@engenty-wizards/shared/dist/knowledge.js +4 -4
  168. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +4 -4
  169. package/node_modules/@engenty-wizards/shared/dist/projects.js +1 -1
  170. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +48 -48
  171. package/node_modules/@engenty-wizards/shared/dist/run.js +48 -48
  172. package/node_modules/@engenty-wizards/shared/dist/space-data.d.ts +2 -2
  173. package/node_modules/@engenty-wizards/shared/dist/surface.d.ts +20 -20
  174. package/node_modules/@engenty-wizards/shared/dist/wizard-package.js +1 -1
  175. package/package.json +5 -4
  176. package/apps/web/dist/assets/AboutDialog-BtAuA44m.js +0 -1
  177. package/apps/web/dist/assets/EditorPage-B4jX_Ckc.js +0 -3
  178. package/apps/web/dist/assets/ReadOnly-DFysMEMF.js +0 -1
  179. package/apps/web/dist/assets/SettingsPage-BKkOfz6P.js +0 -1
  180. package/apps/web/dist/assets/changelog-D8WjOq7d.js +0 -1
  181. package/apps/web/dist/assets/circle-alert-BVj6idzu.js +0 -1
  182. package/apps/web/dist/assets/lock-BoWS-JhN.js +0 -1
  183. package/apps/web/dist/assets/oss-credits-BQQX0kvk.js +0 -1
@@ -31,7 +31,7 @@ export function currentRole() {
31
31
  return actor.getStore() ?? "owner";
32
32
  }
33
33
  export const isAdminRole = (role) => role === "owner" || role === "admin";
34
- export const MEMBERS_EDIT_ONLY = bilingual("Das dürfen nur Admins des Teams. Als Mitglied bearbeitest du die Wizards, die es gibt.", "Only the team's admins may do that. As a member you edit the wizards that are there.");
34
+ export const MEMBERS_EDIT_ONLY = bilingual("Das dürfen nur Admins des Teams. Als Mitglied bearbeitest du die Wizards, die es schon gibt.", "Only team admins can do that. As a member, you can edit the wizards that already exist.");
35
35
  /** Refuses a member: making and deleting is for the owner and the admins of the team. */
36
36
  export async function requireAdmin() {
37
37
  if (syncing() || !managed || isAdminRole(currentRole())) {
@@ -52,8 +52,8 @@ export async function mayBuild() {
52
52
  // An older Manage-App does not say: then it builds. One that cannot be reached: nothing changes.
53
53
  return info ? info.limits.build !== false : false;
54
54
  }
55
- export const READ_ONLY_PROJECT = bilingual("Dieses Projekt kommt aus einer lokalen Installation und wird dort geändert.", "This project comes from a local install and is changed there.");
56
- export const READ_ONLY_TENANT = bilingual("Hier wird nichts gebaut: Auf diesem Server laufen die Wizards, die deine lokale Installation veröffentlicht.", "Nothing is built here: this server runs the wizards your local install publishes.");
55
+ export const READ_ONLY_PROJECT = bilingual("Diesen Space bearbeitest du in engenty wizards auf deinem Computer, nicht hier.", "You edit this space in engenty wizards on your computer, not here.");
56
+ export const READ_ONLY_TENANT = bilingual("Hier laufen die Wizards, die du in engenty wizards auf deinem Computer veröffentlichst. Gebaut und bearbeitet wird dort.", "Wizards run here once you publish them in engenty wizards on your computer. You build and edit them there.");
57
57
  /** Refuses when the tenant builds nothing here. */
58
58
  export async function requireBuild() {
59
59
  if (syncing() || (await mayBuild())) {
@@ -253,7 +253,7 @@ export async function readChat(user, chatId) {
253
253
  where: and(mine(user), eq(schema.assistantChat.id, chatId)),
254
254
  });
255
255
  if (!chat) {
256
- throw new ServiceError("not_found", bilingual("Diesen Chat gibt es nicht.", "There is no such chat."));
256
+ throw new ServiceError("not_found", bilingual("Diesen Chat gibt es nicht.", "This chat doesn’t exist."));
257
257
  }
258
258
  return {
259
259
  id: chat.id,
@@ -66,14 +66,14 @@ export async function describeGradient(brand, text, current) {
66
66
  answer = result.output;
67
67
  }
68
68
  catch {
69
- throw new ServiceError("invalid", bilingual("Daraus ließ sich kein Verlauf machen. Beschreib ihn bitte anders.", "No gradient could be made from that. Please describe it differently."));
69
+ throw new ServiceError("invalid", bilingual("Daraus ließ sich kein Verlauf machen. Beschreib ihn anders.", "Couldn’t make a gradient from that. Try describing it differently."));
70
70
  }
71
71
  const stops = answer.stops
72
72
  .filter((s) => HEX.test(s.color))
73
73
  .slice(0, 12)
74
74
  .map((s) => ({ color: s.color.toLowerCase(), at: Math.min(100, Math.max(0, s.at)) }));
75
75
  if (stops.length < 2) {
76
- throw new ServiceError("invalid", bilingual("Daraus ließ sich kein Verlauf machen. Beschreib ihn bitte anders.", "No gradient could be made from that. Please describe it differently."));
76
+ throw new ServiceError("invalid", bilingual("Daraus ließ sich kein Verlauf machen. Beschreib ihn anders.", "Couldn’t make a gradient from that. Try describing it differently."));
77
77
  }
78
78
  return {
79
79
  name: answer.name.trim().slice(0, 80),
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { bilingual } from "@engenty-wizards/shared/run";
2
3
  import { and, asc, eq, inArray, isNotNull, isNull, like } from "drizzle-orm";
3
4
  import { accountToken, linkedAccount } from "../auth/account.js";
4
5
  import { control, controlDb, db, schema } from "../db/client.js";
@@ -25,7 +26,7 @@ export async function cloudState(wizardId) {
25
26
  async function call(method, path, body, timeoutMs = 30_000) {
26
27
  const token = await accountToken();
27
28
  if (!token) {
28
- throw new ServiceError("refused", "The account is signed out. Please sign in again.", {
29
+ throw new ServiceError("refused", bilingual("Das Konto ist abgemeldet. Melde dich neu an.", "The account is signed out. Sign in again."), {
29
30
  reason: "signed_out",
30
31
  });
31
32
  }
@@ -39,7 +40,7 @@ async function call(method, path, body, timeoutMs = 30_000) {
39
40
  signal: AbortSignal.timeout(timeoutMs),
40
41
  }).catch(() => null);
41
42
  if (!res) {
42
- throw new ServiceError("refused", "The cloud is not reachable right now.", {
43
+ throw new ServiceError("refused", bilingual("Die Cloud ist gerade nicht erreichbar.", "The cloud can’t be reached right now."), {
43
44
  reason: "unreachable",
44
45
  });
45
46
  }
@@ -49,7 +50,7 @@ async function call(method, path, body, timeoutMs = 30_000) {
49
50
  function refusal(answer, fallback) {
50
51
  const { error, reason, ...rest } = answer.body;
51
52
  const message = answer.status === 401
52
- ? "The account is signed out. Please sign in again."
53
+ ? bilingual("Das Konto ist abgemeldet. Melde dich neu an.", "The account is signed out. Sign in again.")
53
54
  : typeof error === "string" && error
54
55
  ? error
55
56
  : fallback;
@@ -213,7 +214,7 @@ export async function syncToCloud(userId, wizardId) {
213
214
  const w = await ownedWizard(userId, wizardId);
214
215
  const version = await publishedVersion(w);
215
216
  if (!version) {
216
- throw new ServiceError("refused", "Publish the wizard first.");
217
+ throw new ServiceError("refused", bilingual("Veröffentliche den Wizard zuerst.", "Publish the wizard first."));
217
218
  }
218
219
  const before = await cloudState(w.id);
219
220
  try {
@@ -1,3 +1,4 @@
1
+ import { bilingual } from "@engenty-wizards/shared/run";
1
2
  import { errorText, NotedError } from "../model-errors.js";
2
3
  const STATUS = {
3
4
  not_found: 404,
@@ -25,4 +26,4 @@ export class ServiceError extends NotedError {
25
26
  return { error: errorText(this, lang), code: this.code, ...this.data };
26
27
  }
27
28
  }
28
- export const notFound = () => new ServiceError("not_found", "not found");
29
+ export const notFound = () => new ServiceError("not_found", bilingual("Das gibt es nicht oder nicht mehr.", "This doesn’t exist or was deleted."));
@@ -256,7 +256,7 @@ export async function dropConverted(fileId) {
256
256
  .where(and(eq(schema.spaceTable.fileId, fileId), eq(schema.spaceTable.kept, false), isNull(schema.spaceTable.originKey)));
257
257
  }
258
258
  /**
259
- * Reads an uploaded document into Wissen: a table per sheet of a workbook, else a page — with
259
+ * Reads an uploaded document into Knowledge: a table per sheet of a workbook, else a page — with
260
260
  * sub-pages when it is long and has headings of its own. The file stays as the original. A file
261
261
  * nothing can read stays a file; UnreadableDocument says why.
262
262
  */
@@ -386,13 +386,13 @@ export async function convertDocument(file, data) {
386
386
  items: [`p:${parentId}`],
387
387
  };
388
388
  }
389
- // --- Kategorien a model fills ---------------------------------------------------------------
389
+ // --- Categories a model fills ---------------------------------------------------------------
390
390
  /**
391
- * Fills the space's Kategorien of an item from its text, where the space has any and nothing
391
+ * Fills the space's categories of an item from its text, where the space has any and nothing
392
392
  * set them yet. Values a person set are never touched. True when anything was set.
393
393
  */
394
394
  export async function fillCategories(projectId, ref, text, name) {
395
- // A column's Kategorie comes from its cells, not from a model.
395
+ // A column's category comes from its cells, not from a model.
396
396
  const categories = (await categoryRows(projectId)).filter((c) => !(c.proposed || c.tableId));
397
397
  if (!(categories.length && text.trim())) {
398
398
  return false;
@@ -443,9 +443,9 @@ export async function fillCategories(projectId, ref, text, name) {
443
443
  const { changed } = await setItemCategories(projectId, ref, input, "model");
444
444
  return changed;
445
445
  }
446
- // --- the Übersicht of a value ---------------------------------------------------------------
446
+ // --- the summary of a value -----------------------------------------------------------------
447
447
  const SUMMARY_ITEMS = 40;
448
- /** What a value's items say, for the model that writes its Übersicht. */
448
+ /** What a value's items say, for the model that writes its summary. */
449
449
  async function itemsOfValue(valueId) {
450
450
  const rows = await db.query.spaceItemCategory.findMany({
451
451
  where: eq(schema.spaceItemCategory.valueId, valueId),
@@ -496,7 +496,7 @@ async function itemsOfValue(valueId) {
496
496
  return { items: out, total: rows.length };
497
497
  }
498
498
  /**
499
- * Writes the Übersicht of a value: what its items are and what they say, so "which Zuschüsse are
499
+ * Writes the summary of a value: what its items are and what they say, so "which Zuschüsse are
500
500
  * there?" finds one page that answers it. Kept on the value and indexed like a page.
501
501
  */
502
502
  export async function writeSummary(valueId) {
@@ -504,12 +504,12 @@ export async function writeSummary(valueId) {
504
504
  where: eq(schema.spaceCategoryValue.id, valueId),
505
505
  });
506
506
  if (!value) {
507
- throw new ServiceError("not_found", bilingual("Diesen Wert gibt es nicht.", "There is no such value."));
507
+ throw new ServiceError("not_found", bilingual("Diesen Wert gibt es nicht.", "This value doesn’t exist."));
508
508
  }
509
509
  const category = await categoryRow(value.categoryId);
510
510
  const { items, total } = await itemsOfValue(value.id);
511
511
  if (!items.length) {
512
- throw new ServiceError("invalid", "Noch steht nichts unter diesem Wert.");
512
+ throw new ServiceError("invalid", bilingual("Unter diesem Wert steht noch nichts.", "Nothing is filed under this value yet."));
513
513
  }
514
514
  const model = await textModel("standard");
515
515
  const result = await generateText({
@@ -530,7 +530,7 @@ export async function writeSummary(valueId) {
530
530
  queueIndex(`v:${value.id}`);
531
531
  return summary;
532
532
  }
533
- /** After a source wrote: the values it filled that have three items or more get their Übersicht. */
533
+ /** After a source wrote: the values it filled that have three items or more get their summary. */
534
534
  export async function summarizeValues(projectId, categoryName, value) {
535
535
  const categories = (await categoryRows(projectId)).filter((c) => c.type === "choice" &&
536
536
  !c.proposed &&
@@ -172,7 +172,7 @@ async function convertedFiles(projectId) {
172
172
  ]);
173
173
  return new Set([...pages, ...tables].map((r) => r.id));
174
174
  }
175
- /** Everything Wissen holds: pages (sub-pages counted under them), tables, files, Kategorien. */
175
+ /** Everything Knowledge holds: pages (sub-pages counted under them), tables, files, categories. */
176
176
  export async function listKnowledge(userId, projectId) {
177
177
  const project = await ownedProject(userId, projectId);
178
178
  const [tree, tables, rowCounts, files, converted, categories, embeddings, checker] = await Promise.all([
@@ -277,7 +277,7 @@ export async function listKnowledge(userId, projectId) {
277
277
  items.sort((a, b) => a.title.localeCompare(b.title, "de"));
278
278
  return { items, categories, embeddings, checked: checker };
279
279
  }
280
- /** A value opened in the studio: its Übersicht, and the items and rows that have it. */
280
+ /** A value opened in the studio: its summary, and the items and rows that have it. */
281
281
  export async function valueContents(userId, valueId) {
282
282
  const value = await db.query.spaceCategoryValue.findFirst({
283
283
  where: eq(schema.spaceCategoryValue.id, valueId),
@@ -303,7 +303,7 @@ export async function valueContents(userId, valueId) {
303
303
  ...(await itemsHaving(userId, project.id, having)),
304
304
  };
305
305
  }
306
- /** Everything that has a Kategorie, whatever its value: what the Kategorie's page lists. */
306
+ /** Everything that has a category, whatever its value: what the category's page lists. */
307
307
  export async function categoryContents(userId, categoryId) {
308
308
  const category = await db.query.spaceCategory.findFirst({
309
309
  where: eq(schema.spaceCategory.id, categoryId),
@@ -377,7 +377,7 @@ async function itemsHaving(userId, projectId, having) {
377
377
  })),
378
378
  };
379
379
  }
380
- /** What the studio shows around a page of Wissen: where it stands, its sub-pages, its origin. */
380
+ /** What the studio shows around a page of Knowledge: where it stands, its sub-pages, its origin. */
381
381
  export async function knowledgePage(page) {
382
382
  const tree = await pageTree(page.projectId);
383
383
  const node = tree.get(page.id);
@@ -443,7 +443,7 @@ function sectionOf(markdown, wanted) {
443
443
  return start >= 0 ? lines.slice(start).join("\n").trim() : null;
444
444
  }
445
445
  /**
446
- * A page as a step reads it: front matter (title, path, Kategorien, where it came from, its
446
+ * A page as a step reads it: front matter (title, path, categories, where it came from, its
447
447
  * original, its parent and sub-pages) and its Markdown, or one section of it. Long pages come in
448
448
  * parts: `from` continues where the last part ended.
449
449
  */
@@ -472,7 +472,7 @@ export async function readPage(projectId, path, opts = {}) {
472
472
  ...(node?.parentId ? [`parent: ${tree.get(node.parentId)?.path}`] : []),
473
473
  ...(lines.length
474
474
  ? [
475
- "kategorien:",
475
+ "categories:",
476
476
  ...lines.map((l) => ` ${l.replace(/^([^:]+): (.*)$/, (_m, k, v) => `${yamlValue(k)}: ${yamlValue(v)}`)}`),
477
477
  ]
478
478
  : []),
@@ -586,15 +586,15 @@ export async function readTable(projectId, path, opts = {}) {
586
586
  };
587
587
  }
588
588
  /**
589
- * For project_list: without a filter, the Kategorien with their values and how many items each
590
- * has; with one, the items it leaves, with their paths and Kategorien.
589
+ * For project_list: without a filter, the categories with their values and how many items each
590
+ * has; with one, the items it leaves, with their paths and categories.
591
591
  */
592
592
  export async function listForStep(projectId, where) {
593
593
  const categories = await listCategories(projectId);
594
594
  const taken = categories.filter((c) => !c.proposed);
595
595
  if (!Object.keys(where ?? {}).length) {
596
596
  return {
597
- kategorien: taken.map((c) => ({
597
+ categories: taken.map((c) => ({
598
598
  name: c.name,
599
599
  type: c.type,
600
600
  ...(c.unit ? { unit: c.unit } : {}),
@@ -640,27 +640,27 @@ export async function listForStep(projectId, where) {
640
640
  .map((n) => ({
641
641
  path: n.path,
642
642
  title: n.title,
643
- kategorien: categoryLines(taken, cats.get(`p:${n.id}`)),
643
+ categories: categoryLines(taken, cats.get(`p:${n.id}`)),
644
644
  })),
645
645
  ...[...(filtered?.tables ?? [])].map((id) => ({
646
646
  path: tPaths.get(id) ?? "",
647
647
  title: "",
648
- kategorien: categoryLines(taken, cats.get(`t:${id}`)),
648
+ categories: categoryLines(taken, cats.get(`t:${id}`)),
649
649
  })),
650
650
  ...[...rowTables].map(([id, n]) => ({
651
651
  path: tPaths.get(id) ?? "",
652
652
  title: `${n} rows match: read them with table_read and the same values as its columns`,
653
- kategorien: [],
653
+ categories: [],
654
654
  })),
655
655
  ...[...(filtered?.files ?? [])].map((id) => ({
656
656
  path: fPaths.get(id) ?? "",
657
657
  title: "",
658
- kategorien: categoryLines(taken, cats.get(`f:${id}`)),
658
+ categories: categoryLines(taken, cats.get(`f:${id}`)),
659
659
  })),
660
660
  ];
661
661
  return { items: items.slice(0, 200), total: items.length };
662
662
  }
663
- /** How much Wissen a space holds: what the block of the steps' instructions says. */
663
+ /** How much Knowledge a space holds: what the block of the steps' instructions says. */
664
664
  export async function knowledgeCounts(projectId) {
665
665
  const [pages, tables, files] = await Promise.all([
666
666
  db
@@ -679,8 +679,8 @@ export async function knowledgeCounts(projectId) {
679
679
  return { pages: pages[0].n, tables: tables[0].n, files: files[0].n };
680
680
  }
681
681
  /**
682
- * What every step of the space is told about Wissen: how much there is, the Kategorien to filter
683
- * by, the tools to find and read. Empty when Wissen is.
682
+ * What every step of the space is told about Knowledge: how much there is, the categories to filter
683
+ * by, the tools to find and read. Empty when Knowledge is.
684
684
  */
685
685
  export async function knowledgeBlock(projectId) {
686
686
  const counts = await knowledgeCounts(projectId);
@@ -708,14 +708,14 @@ export async function knowledgeBlock(projectId) {
708
708
  return `- ${c.name} (${type})${values}`;
709
709
  });
710
710
  return [
711
- "# WISSEN — WHAT THE SPACE KNOWS",
711
+ "# KNOWLEDGE — WHAT THE SPACE KNOWS",
712
712
  `Ground truth the space's owner keeps for all wizards: ${what}. Use it when the task touches what it covers, and prefer it to what you remember.`,
713
713
  ...(lines.length
714
714
  ? [
715
- "Kategorien, to filter with `where` in project_search and project_list before searching:",
715
+ "Categories, to filter with `where` in project_search and project_list before searching:",
716
716
  ...lines,
717
717
  ]
718
718
  : []),
719
- "Find with project_search (a question; `where` on Kategorien). List with project_list. Read whole with page_read and table_read by path. A page marks the pages of its original as <!-- S. n -->: quote them when the source matters.",
719
+ "Find with project_search (a question; `where` on categories). List with project_list. Read whole with page_read and table_read by path. A page marks the pages of its original as <!-- S. n -->: quote them when the source matters.",
720
720
  ].join("\n");
721
721
  }
@@ -404,13 +404,13 @@ export async function marketplaceWizard(id, lang) {
404
404
  return null;
405
405
  }
406
406
  if (!readable(found.definition)) {
407
- throw new ServiceError("refused", bilingual("Diese Vorlage braucht eine neuere Version der App. Bitte aktualisiere die App.", "This template needs a newer version of the app. Please update the app."));
407
+ throw new ServiceError("refused", bilingual("Diese Vorlage braucht eine neuere Version von engenty wizards. Aktualisiere zuerst engenty wizards.", "This template needs a newer version of engenty wizards. Update engenty wizards first."));
408
408
  }
409
409
  // A wizard whose plugins are not here would stop at its first step that uses them.
410
410
  const have = await pluginIdsOf(currentTenant());
411
411
  const needs = pluginsOf(wizardSchema.parse(found.definition)).filter((p) => !have.has(p));
412
412
  if (needs.length) {
413
- throw new ServiceError("refused", bilingual(`Diese Vorlage braucht ${needs.length === 1 ? "das Plugin" : "die Plugins"} ${needs.join(", ")}: mit Pro auf engenty.ai dabei.`, `This template needs the ${needs.length === 1 ? "plugin" : "plugins"} ${needs.join(", ")}: included with Pro on engenty.ai.`));
413
+ throw new ServiceError("refused", bilingual(`Diese Vorlage braucht ${needs.length === 1 ? "das Plugin" : "die Plugins"} ${needs.join(", ")}: bei Pro auf engenty.ai dabei.`, `This template needs the ${needs.length === 1 ? "plugin" : "plugins"} ${needs.join(", ")}: included with Pro on engenty.ai.`));
414
414
  }
415
415
  return {
416
416
  id: found.id,
@@ -1,3 +1,4 @@
1
+ import { bilingual } from "@engenty-wizards/shared/run";
1
2
  import { manifestSchema, PACKAGE_FILES, PACKAGE_FORMAT, PACKAGE_MANIFEST, PACKAGE_MAX_BYTES, } from "@engenty-wizards/shared/wizard-package";
2
3
  import { cleanPath, WORKSPACE_LIMITS } from "@engenty-wizards/shared/workspace";
3
4
  import { strFromU8, unzipSync } from "fflate";
@@ -6,13 +7,13 @@ import { ServiceError } from "./errors.js";
6
7
  * Reading a `.wizard` package, with nothing of a project or a database: the studio's import and
7
8
  * `wizards validate` both start here.
8
9
  */
9
- const invalid = (message) => new ServiceError("invalid", message);
10
+ const invalid = (said) => new ServiceError("invalid", said);
10
11
  export function json(data) {
11
12
  try {
12
13
  return JSON.parse(strFromU8(data));
13
14
  }
14
15
  catch {
15
- throw invalid("This is not a wizard package: wizard.json is not valid JSON.");
16
+ throw invalid(bilingual("Das ist keine Wizard-Datei: Die wizard.json darin lässt sich nicht lesen.", "This isn’t a wizard file: its wizard.json can’t be read."));
16
17
  }
17
18
  }
18
19
  /** A manifest, or a bare definition: a wizard without workspace is fully told by its JSON. */
@@ -22,7 +23,7 @@ export function readManifest(raw) {
22
23
  }
23
24
  const parsed = manifestSchema.safeParse(raw);
24
25
  if (!parsed.success) {
25
- throw invalid("This wizard package comes from a newer version, or it is damaged.");
26
+ throw invalid(bilingual("Diese Wizard-Datei stammt aus einer neueren Version oder ist beschädigt. Aktualisiere engenty wizards oder exportiere den Wizard neu.", "This wizard file comes from a newer version, or it’s damaged. Update engenty wizards or export the wizard again."));
26
27
  }
27
28
  return parsed.data;
28
29
  }
@@ -45,24 +46,26 @@ export function readPackage(data) {
45
46
  total += file.originalSize;
46
47
  count += 1;
47
48
  if (file.originalSize > WORKSPACE_LIMITS.fileBytes) {
48
- throw invalid(`${file.name} is larger than 5 MB.`);
49
+ throw invalid(bilingual(`${file.name} ist größer als 5 MB.`, `${file.name} is larger than 5 MB.`));
49
50
  }
50
51
  if (count > WORKSPACE_LIMITS.files + 1 || total > PACKAGE_MAX_BYTES) {
51
- throw invalid("The package is larger than a workspace may be (200 files, 25 MB).");
52
+ throw invalid(bilingual("Die Wizard-Datei ist zu groß: Ein Arbeitsbereich fasst höchstens 200 Dateien und 25 MB.", "The wizard file is too large: a workspace holds at most 200 files and 25 MB."));
52
53
  }
53
54
  return true;
54
55
  },
55
56
  });
56
57
  }
57
58
  catch (err) {
58
- throw err instanceof ServiceError ? err : invalid("The package cannot be unpacked.");
59
+ throw err instanceof ServiceError
60
+ ? err
61
+ : invalid(bilingual("Die Wizard-Datei lässt sich nicht öffnen. Exportiere den Wizard neu und versuche es noch einmal.", "The wizard file can’t be opened. Export the wizard again and try once more."));
59
62
  }
60
63
  // A package unpacked and zipped again often sits in a folder of its own.
61
64
  const manifestPath = Object.keys(entries)
62
65
  .filter((name) => name === PACKAGE_MANIFEST || name.endsWith(`/${PACKAGE_MANIFEST}`))
63
66
  .sort((a, b) => a.length - b.length)[0];
64
67
  if (manifestPath === undefined) {
65
- throw invalid("This is not a wizard package: wizard.json is missing.");
68
+ throw invalid(bilingual("Das ist keine Wizard-Datei: Die wizard.json fehlt.", "This isn’t a wizard file: wizard.json is missing."));
66
69
  }
67
70
  const root = manifestPath.slice(0, -PACKAGE_MANIFEST.length);
68
71
  const files = Object.entries(entries)
@@ -70,12 +73,12 @@ export function readPackage(data) {
70
73
  .map(([name, content]) => {
71
74
  const path = cleanPath(name.slice(root.length + PACKAGE_FILES.length));
72
75
  if (!path) {
73
- throw invalid(`"${name}" is not a valid file path.`);
76
+ throw invalid(bilingual(`„${name}“ ist kein gültiger Dateipfad.`, `“${name}” isn’t a valid file path.`));
74
77
  }
75
78
  return { path, data: content };
76
79
  });
77
80
  if (files.reduce((n, f) => n + f.data.byteLength, 0) > WORKSPACE_LIMITS.totalBytes) {
78
- throw invalid("The workspace in the package is larger than 25 MB.");
81
+ throw invalid(bilingual("Der Arbeitsbereich in der Wizard-Datei ist größer als 25 MB.", "The workspace in the wizard file is larger than 25 MB."));
79
82
  }
80
83
  return { manifest: readManifest(json(entries[manifestPath])), files };
81
84
  }
@@ -12,7 +12,7 @@ import { ownedProject } from "./projects.js";
12
12
  import { createWizard, deleteWizard, draftIssues, ownedWizard, parseDraft } from "./wizards.js";
13
13
  /** A wizard as a file — the format is `@engenty-wizards/shared/wizard-package`'s. */
14
14
  /** Where the project's pages go in a package, as Markdown the standalone runner reads inline. */
15
- export const KNOWLEDGE_DIR = "wissen/";
15
+ export const KNOWLEDGE_DIR = "knowledge/";
16
16
  /** How much of the project's knowledge a package takes along: enough pages, never the whole space. */
17
17
  export const KNOWLEDGE_MAX_CHARS = 2_000_000;
18
18
  /** What one page may hold in the package (the plugin hands a file to the model up to there). */
@@ -151,8 +151,8 @@ lang = "en") {
151
151
  : `Imported${name ? ` from “${name}”` : ""}: ${draft.steps.length} steps, ${files.length} ${files.length === 1 ? "file" : "files"}.`,
152
152
  lacks.length
153
153
  ? de
154
- ? `Diesem Projekt fehlt noch: ${lacks.join(", ")} – in den Einstellungen unter „Connectors“ hinzufügen.`
155
- : `This project still lacks: ${lacks.join(", ")} – add it under “Connectors” in the settings.`
154
+ ? `Diesem Space fehlt noch: ${lacks.join(", ")}. Füge es unter Einstellungen → Connectors hinzu.`
155
+ : `This space still needs: ${lacks.join(", ")}. Add it under Settings → Connectors.`
156
156
  : "",
157
157
  ]
158
158
  .filter(Boolean)
@@ -28,7 +28,7 @@ export function fileView(row) {
28
28
  description: row.description,
29
29
  source: row.source,
30
30
  status: stale ? "failed" : row.status,
31
- error: stale ? "Interrupted. Please read it in again." : row.error,
31
+ error: stale ? "Reading was interrupted. Read it in again." : row.error,
32
32
  indexed: row.indexed,
33
33
  pages: row.pages,
34
34
  chars: row.chars,
@@ -82,11 +82,11 @@ export async function addProjectFile(projectId, input) {
82
82
  ? bilingual("Ein Logo ist ein Bild (PNG, SVG, JPEG, WebP).", "A logo is an image (PNG, SVG, JPEG, WebP).")
83
83
  : input.kind === "pattern"
84
84
  ? bilingual("Ein Muster ist ein Bild (SVG, PNG, JPEG, WebP).", "A pattern is an image (SVG, PNG, JPEG, WebP).")
85
- : bilingual("Hierher gehören Bilder, Videos, Audio, PDFs, Schriften, Lottie und 3D-Modelle. Texte zum Nachschlagen bitte zu den Dokumenten.", "Images, videos, audio, PDFs, fonts, Lottie and 3D models belong here. Texts to look things up in go to the documents."));
85
+ : bilingual("Hierher gehören Bilder, Videos, Audio, PDFs, Schriften, Lottie und 3D-Modelle. Texte zum Nachschlagen gehören ins Wissen.", "Images, videos, audio, PDFs, fonts, Lottie and 3D models go here. Texts to look things up in go under knowledge."));
86
86
  }
87
87
  const limit = projectFileLimit(input.kind, mime);
88
88
  if (!input.data.byteLength || input.data.byteLength > limit) {
89
- throw new ServiceError("invalid", bilingual(`Bitte eine Datei bis ${Math.round(limit / 1_000_000)} MB wählen.`, `Please choose a file of up to ${Math.round(limit / 1_000_000)} MB.`));
89
+ throw new ServiceError("invalid", bilingual(`Wähl eine Datei bis ${Math.round(limit / 1_000_000)} MB.`, `Choose a file of ${Math.round(limit / 1_000_000)} MB or less.`));
90
90
  }
91
91
  const scope = and(eq(schema.projectFile.projectId, projectId), eq(schema.projectFile.kind, input.kind));
92
92
  const [{ n, last }] = await db
@@ -94,7 +94,7 @@ export async function addProjectFile(projectId, input) {
94
94
  .from(schema.projectFile)
95
95
  .where(scope);
96
96
  if (n >= PROJECT_LIMITS.files[input.kind]) {
97
- throw new ServiceError("refused", bilingual(`Höchstens ${PROJECT_LIMITS.files[input.kind]} Dateien.`, `At most ${PROJECT_LIMITS.files[input.kind]} files.`));
97
+ throw new ServiceError("refused", bilingual(`Hier haben höchstens ${PROJECT_LIMITS.files[input.kind]} Dateien Platz.`, `There’s room for at most ${PROJECT_LIMITS.files[input.kind]} files here.`));
98
98
  }
99
99
  const name = input.name.slice(0, 120) || "datei";
100
100
  const ref = await saveAsset({
@@ -150,15 +150,15 @@ export async function addProjectFileFromUrl(projectId, input) {
150
150
  headers: { "user-agent": "Mozilla/5.0 (compatible; engenty-wizards/0.1)" },
151
151
  signal: AbortSignal.any([AbortSignal.timeout(60_000), ...(signal ? [signal] : [])]),
152
152
  }).catch(() => {
153
- throw new ServiceError("invalid", bilingual("Die Adresse ist nicht erreichbar.", "The address cannot be reached."));
153
+ throw new ServiceError("invalid", bilingual("Die Adresse ist nicht erreichbar.", "The address can’t be reached."));
154
154
  });
155
155
  if (!res.ok) {
156
- throw new ServiceError("invalid", bilingual(`Die Adresse antwortete ${res.status}.`, `The address answered ${res.status}.`));
156
+ throw new ServiceError("invalid", bilingual(`Die Adresse antwortete mit ${res.status}.`, `The address responded with ${res.status}.`));
157
157
  }
158
158
  const mime = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
159
159
  if (mime === "text/html" && kind !== "document") {
160
160
  await res.body?.cancel();
161
- throw new ServiceError("invalid", bilingual("Unter der Adresse steht eine Webseite, kein Bild.", "The address holds a web page, not an image."), {
161
+ throw new ServiceError("invalid", bilingual("Unter der Adresse steht eine Webseite, kein Bild.", "The address leads to a web page, not an image."), {
162
162
  reason: "page",
163
163
  url: res.url,
164
164
  });
@@ -206,7 +206,7 @@ async function describeImage(row, data) {
206
206
  maxOutputTokens: 300,
207
207
  system: [
208
208
  "You catalogue the files of a project. Describe the picture in ONE sentence of at most 25 words: what it shows, its style and colours, and what it suits (a logo on dark ground, a header image, a team photo, a product shot).",
209
- "Write in the language of the project's description; if unclear, German. No preamble, no quotes.",
209
+ "Write in the language of the project's description; if unclear, English. No preamble, no quotes.",
210
210
  ].join("\n"),
211
211
  messages: [
212
212
  {
@@ -237,7 +237,7 @@ async function describeDocument(row, text) {
237
237
  return result.text.trim().slice(0, 600);
238
238
  }
239
239
  /**
240
- * What a document became gets its Kategorien from a model, where the space has some: the first
240
+ * What a document became gets its categories from a model, where the space has some: the first
241
241
  * page or table it was read into, else the file itself.
242
242
  */
243
243
  async function categorize(row, items, text) {
@@ -248,7 +248,7 @@ async function categorize(row, items, text) {
248
248
  ? { tableId: first.slice(2) }
249
249
  : { fileId: row.id };
250
250
  const changed = await fillCategories(row.projectId, ref, text || row.description, row.name).catch((err) => {
251
- console.error("[project-file] Kategorien", row.name, err);
251
+ console.error("[project-file] categories", row.name, err);
252
252
  return false;
253
253
  });
254
254
  if (changed) {
@@ -256,7 +256,7 @@ async function categorize(row, items, text) {
256
256
  }
257
257
  }
258
258
  /**
259
- * Reads a new file. A document is read into Wissen — a page, a page with sub-pages, a table per
259
+ * Reads a new file. A document is read into Knowledge — a page, a page with sub-pages, a table per
260
260
  * sheet — and stays as the original behind it; one that reads into nothing stays a file, found
261
261
  * by its name and description. A file nobody described is described.
262
262
  */
@@ -296,9 +296,7 @@ async function prepare(row) {
296
296
  console.error("[project-file]", row.name, err);
297
297
  await set({
298
298
  status: "failed",
299
- error: err instanceof UnreadableDocument
300
- ? err.message
301
- : "Reading did not work. Please try again.",
299
+ error: err instanceof UnreadableDocument ? err.message : "Reading didn’t work. Try again.",
302
300
  });
303
301
  }
304
302
  });
@@ -370,7 +368,7 @@ export async function removeProjectFiles(projectId) {
370
368
  }
371
369
  // --- what plugins keep as files ----------------------------------------------------
372
370
  /**
373
- * A plugin's file of Wissen (`server.spaceData.putFile`, or the original of a page it wrote):
371
+ * A plugin's file of Knowledge (`server.spaceData.putFile`, or the original of a page it wrote):
374
372
  * a document of the space under the plugin's key. The same key again replaces it. `text`: what
375
373
  * a step reads of it; without one only its name and description are found.
376
374
  */
@@ -382,7 +380,7 @@ export async function putOriginFile(origin, input) {
382
380
  });
383
381
  const mime = documentMime(input.name, input.mime) || "application/octet-stream";
384
382
  if (!input.data.byteLength || input.data.byteLength > projectFileLimit("document", mime)) {
385
- throw new ServiceError("invalid", `"${input.name}" is empty or larger than a document may be.`);
383
+ throw new ServiceError("invalid", bilingual(`„${input.name}“ ist leer oder zu groß für ein Dokument.`, `“${input.name}” is empty or too large for a document.`));
386
384
  }
387
385
  const name = input.name.slice(0, 120) || "datei";
388
386
  const values = {
@@ -409,7 +407,7 @@ export async function putOriginFile(origin, input) {
409
407
  .from(schema.projectFile)
410
408
  .where(and(eq(schema.projectFile.projectId, input.space), eq(schema.projectFile.kind, "document")));
411
409
  if (n >= PROJECT_LIMITS.files.document) {
412
- throw new ServiceError("refused", `A space keeps at most ${PROJECT_LIMITS.files.document} documents.`);
410
+ throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${PROJECT_LIMITS.files.document} Dokumente.`, `A space can have at most ${PROJECT_LIMITS.files.document} documents.`));
413
411
  }
414
412
  const ref = await saveAsset({ kind: "project", mime, name, data: input.data });
415
413
  id = ref.id;
@@ -159,7 +159,7 @@ async function indexPage(pageId) {
159
159
  const prefix = head(node?.titles ?? [page.title], lines);
160
160
  const sections = sectionsOf(page.markdown);
161
161
  const units = [];
162
- // A page without text is still found by its title and Kategorien.
162
+ // A page without text is still found by its title and categories.
163
163
  for (const section of sections.length ? sections : [{ heading: [], sheet: null, body: "" }]) {
164
164
  // A page's own first heading is its title: the path says it already.
165
165
  const path = section.heading[0]?.toLowerCase() === page.title.toLowerCase()
@@ -244,7 +244,7 @@ async function indexFile(fileId) {
244
244
  .select({ heading: schema.projectChunk.heading, idx: schema.projectChunk.idx })
245
245
  .from(schema.projectChunk)
246
246
  .where(scope);
247
- // Passages of a document read before Wissen had pages stay until it is read again.
247
+ // Passages of a document read before Knowledge had pages stay until it is read again.
248
248
  if (passages.some((p) => p.heading === null)) {
249
249
  return;
250
250
  }
@@ -279,7 +279,7 @@ async function indexValue(valueId) {
279
279
  valueId: value.id,
280
280
  heading: `${category.name}: ${value.value}`,
281
281
  idx: i,
282
- text: `${category.name}: ${value.value} — Übersicht\n\n${part}`,
282
+ text: `${category.name}: ${value.value} — Summary\n\n${part}`,
283
283
  }));
284
284
  await writeUnits(scope, units);
285
285
  }
@@ -294,8 +294,8 @@ async function subPages(pageId) {
294
294
  }
295
295
  /**
296
296
  * Brings one item's units up to date. `p:` a page, `P:` a page and its sub-pages (its title or
297
- * Kategorien changed: they stand in theirs), `t:` a table's rows, `r:` one row, `f:` a file, `v:`
298
- * a value's Übersicht.
297
+ * categories changed: they stand in theirs), `t:` a table's rows, `r:` one row, `f:` a file, `v:`
298
+ * a value's summary.
299
299
  */
300
300
  export async function indexItem(key) {
301
301
  const id = key.slice(2);
@@ -423,7 +423,7 @@ export function trigramQuery(query) {
423
423
  return terms.length ? terms.join(" OR ") : null;
424
424
  }
425
425
  const COLUMNS = sql.raw("c.id, c.file_id, c.page_id, c.row_id, c.table_id, c.value_id, c.heading, c.sheet, c.plugin, c.ref, c.title, c.link, c.idx, c.text");
426
- /** A unit of the space's own Wissen, or of a plugin's text while the tenant has that plugin. */
426
+ /** A unit of the space's own Knowledge, or of a plugin's text while the tenant has that plugin. */
427
427
  function searchable(plugins) {
428
428
  return plugins.length
429
429
  ? sql `(c.plugin IS NULL OR c.plugin IN (${sql.join(plugins.map((p) => sql `${p}`), sql `, `)}))`
@@ -465,7 +465,7 @@ function itemOf(u) {
465
465
  return `x:${u.plugin}:${u.ref}`;
466
466
  }
467
467
  /**
468
- * Searches Wissen: items whose Kategorien pass `where`, then the units the keywords, the
468
+ * Searches Knowledge: items whose categories pass `where`, then the units the keywords, the
469
469
  * trigrams and the vectors rank best, merged by rank (RRF), then the classifier's judgement of
470
470
  * each of the CANDIDATES best. Gives up to `limit` items with their best section. `check: false`
471
471
  * leaves the classifier out.
@@ -478,7 +478,7 @@ export async function searchKnowledge(projectId, query, opts = {}) {
478
478
  }
479
479
  const plugins = [...(await pluginIdsOf(currentTenant()))];
480
480
  const inFilter = filter ? filtered(filter) : null;
481
- // A filter keeps plugins' texts and Übersichten out: they have no Kategorien.
481
+ // A filter keeps plugins' texts and summaries out: they have no categories.
482
482
  const where = and(sql `c.project_id = ${projectId}`, searchable(plugins), filter
483
483
  ? (inFilter ?? sql `(c.page_id IS NOT NULL OR c.row_id IS NOT NULL OR c.file_id IS NOT NULL)`)
484
484
  : undefined);
@@ -559,7 +559,7 @@ export async function searchKnowledge(projectId, query, opts = {}) {
559
559
  filtered: filter?.size ?? null,
560
560
  };
561
561
  }
562
- /** What a step and the studio see of each unit: the item's path, title and Kategorien. */
562
+ /** What a step and the studio see of each unit: the item's path, title and categories. */
563
563
  async function describe(projectId, units) {
564
564
  const pageIds = [...new Set(units.flatMap((u) => (u.page_id ? [u.page_id] : [])))];
565
565
  const rowIds = [...new Set(units.flatMap((u) => (u.row_id ? [u.row_id] : [])))];
@@ -654,7 +654,7 @@ async function describe(projectId, units) {
654
654
  kind: "summary",
655
655
  open: `v:${u.value_id}`,
656
656
  path: "",
657
- title: `${category?.name}: ${value?.value} — Übersicht`,
657
+ title: `${category?.name}: ${value?.value} — Summary`,
658
658
  categories: [],
659
659
  };
660
660
  }