@plitzi/cli 0.38.9 → 0.38.11

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.
package/dist/index.js CHANGED
@@ -14,8 +14,8 @@ import readline from "node:readline/promises";
14
14
  import { EXPLAIN_KINDS, PAGE_SPEC_FIELDS, PAGE_TEXT_LIMIT, actionSpecFromEntry, actionToSource, authorSpace, blankTemplateFiles, catalogTemplateFiles, closest, comparePictures, compareTexts, darkScheme, dataIssues, elementTriggers, elementTypeNames, emptyTemplateFiles, explain, explainKindOf, explainList, explanationText, failedFlowText, importProbe, importedFiles, inspectDocument, inspectPage, isSuggestionCode, loadImages, locateClasses, locateElements, openPage, pageRegions, pageTexts, planFixes, readDevTools, refusalOf, unrollPage } from "@plitzi/sdk-authoring";
15
15
  import { existsSync, readFileSync, readdirSync, realpathSync, statSync, watch } from "node:fs";
16
16
  import { parseEnv, promisify } from "node:util";
17
- import { lookup } from "node:dns/promises";
18
17
  import esbuild, { build } from "esbuild";
18
+ import { lookup } from "node:dns/promises";
19
19
  import { unzipSync, zipSync } from "fflate";
20
20
  import { gzipSync } from "node:zlib";
21
21
  import { emitKeypressEvents } from "node:readline";
@@ -24,6 +24,9 @@ var t$5 = `[data-plitzi-devtools] { display: none !important; }`;
24
24
  //#region ../../packages/sdk-shared/dist/helpers/isRecord.mjs
25
25
  var e$3 = (e) => typeof e == "object" && !!e && !Array.isArray(e);
26
26
  //#endregion
27
+ //#region ../../packages/sdk-shared/dist/realtime/topics.mjs
28
+ var m$2 = "plitzi-observer";
29
+ //#endregion
27
30
  //#region ../../packages/sdk-shared/dist/theme/themeCookie.mjs
28
31
  var t$3 = "theme";
29
32
  //#endregion
@@ -148,11 +151,15 @@ var openProjectPage = async (browser, origin, view) => {
148
151
  colorScheme: view.scheme ?? "light",
149
152
  reducedMotion: view.reducedMotion ? "reduce" : "no-preference"
150
153
  });
151
- if (view.scheme) await page.context().addCookies([{
154
+ await page.context().addCookies([...view.scheme ? [{
152
155
  name: t$3,
153
156
  value: view.scheme,
154
157
  url: origin
155
- }]);
158
+ }] : [], ...view.presence ? [] : [{
159
+ name: m$2,
160
+ value: "1",
161
+ url: origin
162
+ }]]);
156
163
  return page;
157
164
  };
158
165
  /**
@@ -1354,7 +1361,7 @@ var n$3 = [
1354
1361
  ".otf"
1355
1362
  ];
1356
1363
  var r$5 = () => Object.fromEntries(n$3.map((e) => [e, "dataurl"]));
1357
- var i$5 = /\?(raw|inline)$/;
1364
+ var i$4 = /\?(raw|inline)$/;
1358
1365
  var a$5 = {
1359
1366
  ".wasm": "application/wasm",
1360
1367
  ".js": "text/javascript",
@@ -1371,8 +1378,8 @@ var a$5 = {
1371
1378
  var o$4 = () => ({
1372
1379
  name: "plitzi-import-queries",
1373
1380
  setup: (n) => {
1374
- n.onResolve({ filter: i$5 }, async (e) => {
1375
- let t = i$5.exec(e.path)?.[1] ?? "raw", r = await n.resolve(e.path.replace(i$5, ""), {
1381
+ n.onResolve({ filter: i$4 }, async (e) => {
1382
+ let t = i$4.exec(e.path)?.[1] ?? "raw", r = await n.resolve(e.path.replace(i$4, ""), {
1376
1383
  kind: e.kind,
1377
1384
  importer: e.importer,
1378
1385
  resolveDir: e.resolveDir
@@ -1405,7 +1412,7 @@ var o$4 = () => ({
1405
1412
  //#region src/scaffold/pluginPackage/names.ts
1406
1413
  var PREFIX = "plitzi-plugin-";
1407
1414
  /** npm's own rule for a package name, scope included. */
1408
- var PACKAGE_NAME = /^(@[a-z0-9][a-z0-9-._~]*\/)?[a-z0-9][a-z0-9-._~]*$/;
1415
+ var PACKAGE_NAME$1 = /^(@[a-z0-9][a-z0-9-._~]*\/)?[a-z0-9][a-z0-9-._~]*$/;
1409
1416
  /** What the name has to leave once the scope and prefix are off: words of letters and digits, starting with a letter. */
1410
1417
  var BASE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
1411
1418
  var baseOf = (packageName) => {
@@ -1414,7 +1421,7 @@ var baseOf = (packageName) => {
1414
1421
  };
1415
1422
  /** What is wrong with a name as a plugin's, in words that say how to fix it — or `undefined` when nothing is. */
1416
1423
  var pluginNameProblem = (packageName) => {
1417
- if (!PACKAGE_NAME.test(packageName)) return `"${packageName}" is not a package name npm accepts: lowercase letters, digits and dashes, optionally under a @scope/.`;
1424
+ if (!PACKAGE_NAME$1.test(packageName)) return `"${packageName}" is not a package name npm accepts: lowercase letters, digits and dashes, optionally under a @scope/.`;
1418
1425
  if (!BASE.test(baseOf(packageName))) return `"${packageName}" has to name the plugin in words — lowercase letters and digits between single dashes, starting with a letter (seat-picker, chart2) — since the component and the type a space uses are made from it.`;
1419
1426
  const { type } = pluginNames(packageName);
1420
1427
  if (elementTypeNames.includes(type)) return `"${packageName}" would make the type "${type}", which is a built-in element: a space could not tell the two apart. Name it after what it does (seat-picker, sales-chart).`;
@@ -2512,7 +2519,7 @@ var YARN_IGNORES$1 = "\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases
2512
2519
  * the CLI records about it, and is committed — a clone without it could not pull, push or upgrade.
2513
2520
  */
2514
2521
  var gitignore$1 = ({ mode, packageManager }) => `node_modules\n${v$3}\n.env\ntmp\n${mode === "server" ? `${y$2}\n` : ""}${packageManager === "yarn" ? YARN_IGNORES$1 : ""}`;
2515
- var startLine = ({ mode, packageManager, source }) => mode === "server" ? `\`${runCommand(packageManager, "start")}\` serves pages on http://127.0.0.1:8080 — or, when something else holds 8080, the next free port, which it prints and writes to \`${x$3}\` (set \`PORT\` to choose one). \`${runCommand(packageManager, "start:dev")}\` restarts on a save to the server's code; a save to a plugin swaps it in the open page${source === "local" ? ", and a save to the space reloads it" : ""}. In production, \`${runCommand(packageManager, "build")}\` once and run \`${runCommand(packageManager, "start:prod")}\`, which sets \`NODE_ENV=production\` itself: the compiled server, with no TypeScript in the process.` : `\`${runCommand(packageManager, "start")}\` runs Vite on http://127.0.0.1:5173, with hot module replacement.`;
2522
+ var startLine = ({ mode, packageManager, source }) => mode === "server" ? `\`${runCommand(packageManager, "start")}\` serves pages on http://127.0.0.1:8080 — or, when something else holds 8080, the next free port, which it prints and writes to \`${x$3}\` (set \`PORT\` to choose one; \`HOST=0.0.0.0\` opens it to your network — a tablet on the same Wi-Fi — and prints the address to open there). \`${runCommand(packageManager, "start:dev")}\` restarts on a save to the server's code; a save to a plugin swaps it in the open page${source === "local" ? ", and a save to the space reloads it" : ""}. In production, \`${runCommand(packageManager, "build")}\` once and run \`${runCommand(packageManager, "start:prod")}\`, which sets \`NODE_ENV=production\` itself: the compiled server, with no TypeScript in the process.` : `\`${runCommand(packageManager, "start")}\` runs Vite on http://127.0.0.1:5173, with hot module replacement.`;
2516
2523
  var spaceSection = (answers) => {
2517
2524
  if (answers.source === "cloud") return `## Where the space comes from
2518
2525
 
@@ -2656,7 +2663,7 @@ ${code(".claude/skills/plitzi-cli/SKILL.md")} first: ${code("plitzi plugin add")
2656
2663
 
2657
2664
  What you leave behind is the next reader's problem — the user's, or the next agent's. Before calling a change done:
2658
2665
 
2659
- - **Nothing unused.** Delete what you made and no longer use — a file, a page, a component, a class, a token, a data file, an import, a plugin folder. No commented-out code, no ${code("console.log")} left from debugging, no copy of a file kept "just in case": git keeps the history.
2666
+ - **Nothing unused.** Delete what you made and no longer use — a file, a page, a component, a class, a token, a data file, an import, a plugin folder. A folder's ${code(".gitkeep")} keeps it in git while it is empty; once the folder holds files it does nothing, and ${code("doctor")} and ${code("upgrade")} ask for it no more. No commented-out code, no ${code("console.log")} left from debugging, no copy of a file kept "just in case": git keeps the history.
2660
2667
  - **Scratch goes in ${code(`tmp/`)}, or nowhere.** A one-off script, a dump, a picture to look at — never at the root or beside the source, where it reads as part of the project.
2661
2668
  - **One of everything.** A look used twice is a class; a value used twice is a token; a block used twice is a component, and rows of data are one list. Change it where it is defined, and rename everywhere when you rename.
2662
2669
  - **Files a reader can find.** One part per file, named after what it is, in the folder of its kind — the shape ${code("src/space/")} already has. Do not start a parallel layout of your own.
@@ -2724,6 +2731,12 @@ PLITZI_SIGNING_SECRET=
2724
2731
 
2725
2732
  # Left out: 8080, or the next free port while developing.
2726
2733
  # PORT=8080
2734
+
2735
+ # Left out: this machine only. 0.0.0.0 opens it to the network — a tablet on the same Wi-Fi, and anyone else on it.
2736
+ # HOST=0.0.0.0
2737
+
2738
+ # The secrets the space's actions and functions name — ctx.fetch's credential, a connector's: credential id → its keys.
2739
+ # PLITZI_CREDENTIALS={"google":{"clientId":"…","clientSecret":"…"}}
2727
2740
  `;
2728
2741
  /** A `.env` with the signing key `plitzi create` made for the project in its place. */
2729
2742
  var withSigningSecret = (env, secret) => env.replace(/^PLITZI_SIGNING_SECRET=$/m, `PLITZI_SIGNING_SECRET=${secret}`);
@@ -2908,6 +2921,8 @@ var serverOptionsModule = (local) => `import type { ProjectServerOptions } from
2908
2921
  *
2909
2922
  * - \`images: { domains: ['images.example.com'] }\` — pictures from those hosts resized here, with \`sharp\` installed.
2910
2923
  ${local ? ACTION_OPTIONS_DOC : ""} * - \`rsc: { elementTimeoutMs }\` — how long a section resolved on the server is waited for: 5 s by default.
2924
+ * - \`devMode\` — the development build, the dev tools, a public action's trace: on unless \`NODE_ENV\` is \`production\`.
2925
+ * \`devMode: false\` keeps them off wherever the server is started, \`NODE_ENV\` or not. \`devReload\` follows it.
2911
2926
  */
2912
2927
  export const serverOptions: ProjectServerOptions = {};
2913
2928
  `;
@@ -4425,15 +4440,15 @@ var __exportAll = (all, no_symbols) => {
4425
4440
  if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
4426
4441
  return target;
4427
4442
  };
4428
- var e$42 = "[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*";
4429
- var t$63 = `(?:${e$42}|\\d+)`;
4430
- var n$47 = `\\[\\s*(?:\\d+|'[^']*'|"[^"]*"|${e$42}(?:\\.${t$63})*)\\s*\\]`;
4431
- var r$36 = `${`${e$42}(?:${n$47})*(?:\\??\\.${t$63}(?:${n$47})*)*`}(?:\\s*\\?\\?\\s*[^|{}]+?)?(?:\\s*\\|\\s*[a-zA-Z_]+(?:\\([^)]*\\))?)*`;
4432
- var i$28 = `\\{\\{\\s*${r$36}\\s*\\}\\}`;
4433
- var a$24 = `(?:${`\\{\\{\\{\\s*${r$36}\\s*\\}\\}\\}`}|${i$28})`;
4443
+ var e$43 = "[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*";
4444
+ var t$63 = `(?:${e$43}|\\d+)`;
4445
+ var n$47 = `\\[\\s*(?:\\d+|'[^']*'|"[^"]*"|${e$43}(?:\\.${t$63})*)\\s*\\]`;
4446
+ var r$36 = `${`${e$43}(?:${n$47})*(?:\\??\\.${t$63}(?:${n$47})*)*`}(?:\\s*\\?\\?\\s*[^|{}]+?)?(?:\\s*\\|\\s*[a-zA-Z_]+(?:\\([^)]*\\))?)*`;
4447
+ var i$29 = `\\{\\{\\s*${r$36}\\s*\\}\\}`;
4448
+ var a$24 = `(?:${`\\{\\{\\{\\s*${r$36}\\s*\\}\\}\\}`}|${i$29})`;
4434
4449
  new RegExp(a$24, "g");
4435
4450
  RegExp(`^${a$24}$`);
4436
- var e$41 = ["smooth", "auto"];
4451
+ var e$42 = ["smooth", "auto"];
4437
4452
  var t$61 = [
4438
4453
  "start",
4439
4454
  "center",
@@ -4445,7 +4460,7 @@ var r$34 = {
4445
4460
  type: "select",
4446
4461
  description: "How it moves: \"smooth\" glides there, \"auto\" jumps.",
4447
4462
  default: "smooth",
4448
- options: [...e$41]
4463
+ options: [...e$42]
4449
4464
  };
4450
4465
  var a$22 = {
4451
4466
  setState: {
@@ -4561,7 +4576,7 @@ var a$22 = {
4561
4576
  }
4562
4577
  }
4563
4578
  };
4564
- var e$40 = {
4579
+ var e$41 = {
4565
4580
  onLoad: {
4566
4581
  action: "onLoad",
4567
4582
  title: "On Load",
@@ -4731,7 +4746,7 @@ var e$40 = {
4731
4746
  params: {}
4732
4747
  }
4733
4748
  };
4734
- var e$39 = () => (e) => e;
4749
+ var e$40 = () => (e) => e;
4735
4750
  var t$60 = () => (e) => e;
4736
4751
  var t$59 = t$60()({
4737
4752
  type: "blockHtml",
@@ -4964,13 +4979,13 @@ var n$44 = {
4964
4979
  var r$33 = t$60()({
4965
4980
  type: "button",
4966
4981
  attributeValues: {
4967
- subType: e$39()([
4982
+ subType: e$40()([
4968
4983
  "button",
4969
4984
  "submit",
4970
4985
  "reset"
4971
4986
  ]),
4972
- contentPlacement: e$39()(["before", "after"]),
4973
- iconPlacement: e$39()(["before", "after"])
4987
+ contentPlacement: e$40()(["before", "after"]),
4988
+ iconPlacement: e$40()(["before", "after"])
4974
4989
  },
4975
4990
  content: {
4976
4991
  attributes: {
@@ -5038,7 +5053,7 @@ var r$33 = t$60()({
5038
5053
  });
5039
5054
  var n$43 = t$60()({
5040
5055
  type: "dropdown",
5041
- attributeValues: { popupPlacement: e$39()([
5056
+ attributeValues: { popupPlacement: e$40()([
5042
5057
  "left",
5043
5058
  "right",
5044
5059
  "top",
@@ -5169,7 +5184,7 @@ var t$54 = t$60()({
5169
5184
  });
5170
5185
  var n$42 = t$60()({
5171
5186
  type: "heading",
5172
- attributeValues: { subType: e$39()([
5187
+ attributeValues: { subType: e$40()([
5173
5188
  "h1",
5174
5189
  "h2",
5175
5190
  "h3",
@@ -5291,20 +5306,20 @@ var n$42 = t$60()({
5291
5306
  var n$41 = t$60()({
5292
5307
  type: "link",
5293
5308
  attributeValues: {
5294
- mode: e$39()([
5309
+ mode: e$40()([
5295
5310
  "page",
5296
5311
  "internal",
5297
5312
  "external"
5298
5313
  ]),
5299
- target: e$39()([
5314
+ target: e$40()([
5300
5315
  "self",
5301
5316
  "blank",
5302
5317
  "parent",
5303
5318
  "top"
5304
5319
  ]),
5305
- contentPlacement: e$39()(["before", "after"]),
5306
- iconPlacement: e$39()(["before", "after"]),
5307
- current: e$39()(["page", "section"])
5320
+ contentPlacement: e$40()(["before", "after"]),
5321
+ iconPlacement: e$40()(["before", "after"]),
5322
+ current: e$40()(["page", "section"])
5308
5323
  },
5309
5324
  content: {
5310
5325
  attributes: {
@@ -5476,7 +5491,7 @@ var t$52 = t$60()({
5476
5491
  });
5477
5492
  var n$40 = t$60()({
5478
5493
  type: "richText",
5479
- attributeValues: { format: e$39()([
5494
+ attributeValues: { format: e$40()([
5480
5495
  "html",
5481
5496
  "markdown",
5482
5497
  "text"
@@ -5613,7 +5628,7 @@ var n$39 = {
5613
5628
  };
5614
5629
  var r$32 = t$60()({
5615
5630
  type: "themeToggle",
5616
- attributeValues: { subType: e$39()(["switch", "segmented"]) },
5631
+ attributeValues: { subType: e$40()(["switch", "segmented"]) },
5617
5632
  triggers: { onThemeChange: {
5618
5633
  action: "onThemeChange",
5619
5634
  title: "On Theme Change",
@@ -5679,7 +5694,7 @@ var r$32 = t$60()({
5679
5694
  });
5680
5695
  var n$38 = t$60()({
5681
5696
  type: "form",
5682
- attributeValues: { method: e$39()(["get", "post"]) },
5697
+ attributeValues: { method: e$40()(["get", "post"]) },
5683
5698
  sourceType: "apiContainer",
5684
5699
  triggers: { onSubmit: {
5685
5700
  action: "onSubmit",
@@ -5813,7 +5828,7 @@ var r$31 = {
5813
5828
  ...n$37,
5814
5829
  "line-height": "20px"
5815
5830
  };
5816
- var i$25 = {
5831
+ var i$26 = {
5817
5832
  display: "flex",
5818
5833
  cursor: "pointer",
5819
5834
  "font-weight": "600",
@@ -5823,7 +5838,7 @@ var i$25 = {
5823
5838
  "user-select": "none"
5824
5839
  };
5825
5840
  var a$21 = {
5826
- ...i$25,
5841
+ ...i$26,
5827
5842
  "margin-bottom": "4px"
5828
5843
  };
5829
5844
  var o$23 = {
@@ -5857,7 +5872,7 @@ var l$13 = {
5857
5872
  "font-size": "inherit",
5858
5873
  "box-shadow": "none"
5859
5874
  };
5860
- var u$13 = {
5875
+ var u$14 = {
5861
5876
  height: "100%",
5862
5877
  width: "100%",
5863
5878
  display: "block",
@@ -5941,7 +5956,7 @@ var m$7 = (e, t, { field: n, icon: r } = {}) => ({
5941
5956
  var h$4 = { field: l$13 };
5942
5957
  var g$4 = t$60()({
5943
5958
  type: "formControl",
5944
- attributeValues: { subType: e$39()([
5959
+ attributeValues: { subType: e$40()([
5945
5960
  "text",
5946
5961
  "number",
5947
5962
  "email",
@@ -5996,7 +6011,7 @@ var g$4 = t$60()({
5996
6011
  definition: {
5997
6012
  label: "Form Control",
5998
6013
  type: "formControl",
5999
- description: "A single labelled input (text/select/checkbox/… per its subType) inside a form; captures one field of user input. A select offers its `options`: `[{ label, value }, …]` — the text shown, and what the field takes. A `switch` is a checkbox announced and drawn as an on/off switch (`role=\"switch\"`), for a setting that applies at once; both hold `true`/`false`, and a `defaultValue` of `\"true\"` starts them on. Its slots: `label`, `requiredMark` (the `*` that ends the label of a required field), `input` (the box a typed field or a select is drawn in — a textarea, checkbox or switch is its own box), `field` (the `<input>` or `<select>` inside that box), `icon` (the show/hide button of a password) and `error`. The thumb of a switch is no element a class reaches: the class on its `input` slot sets `--plitzi-switch-thumb` (off), `--plitzi-switch-thumb-checked` (on, white by default) and `--plitzi-switch-thumb-shadow`.",
6014
+ description: "A single labelled input (text/select/checkbox/… per its subType) inside a form; captures one field of user input. Optional unless `required: true`, as an HTML field is: a `requiredMessage` alone requires nothing. A select offers its `options`: `[{ label, value }, …]` — the text shown, and what the field takes. A `switch` is a checkbox announced and drawn as an on/off switch (`role=\"switch\"`), for a setting that applies at once; both hold `true`/`false`, and a `defaultValue` of `\"true\"` starts them on. Its slots: `label`, `requiredMark` (the `*` that ends the label of a required field), `input` (the box a typed field or a select is drawn in — a textarea, checkbox or switch is its own box), `field` (the `<input>` or `<select>` inside that box), `icon` (the show/hide button of a password) and `error`. The box is ringed (`2px solid currentColor`) while the field inside has keyboard focus: restyle it with a `focus-within` state on the `input` slot — the box never takes `focus` itself. The thumb of a switch is no element a class reaches: the class on its `input` slot sets `--plitzi-switch-thumb` (off), `--plitzi-switch-thumb-checked` (on, white by default) and `--plitzi-switch-thumb-shadow`.",
6000
6015
  bindings: {},
6001
6016
  styleSelectors: {
6002
6017
  base: "",
@@ -6051,7 +6066,7 @@ var g$4 = t$60()({
6051
6066
  color: "inherit",
6052
6067
  resize: "vertical"
6053
6068
  }),
6054
- select: m$7("Form Control Select", f$10, { field: u$13 }),
6069
+ select: m$7("Form Control Select", f$10, { field: u$14 }),
6055
6070
  checkbox: {
6056
6071
  name: "Form Control Checkbox",
6057
6072
  displayMode: "desktop",
@@ -6063,7 +6078,7 @@ var g$4 = t$60()({
6063
6078
  "margin-left": "0px",
6064
6079
  "margin-right": "4px"
6065
6080
  } },
6066
- label: { default: i$25 },
6081
+ label: { default: i$26 },
6067
6082
  requiredMark: { default: s$20 },
6068
6083
  error: { default: o$23 }
6069
6084
  }
@@ -6075,7 +6090,7 @@ var g$4 = t$60()({
6075
6090
  base: { default: {} },
6076
6091
  input: { default: p$11 },
6077
6092
  label: { default: {
6078
- ...i$25,
6093
+ ...i$26,
6079
6094
  "align-items": "center"
6080
6095
  } },
6081
6096
  requiredMark: { default: s$20 },
@@ -6089,7 +6104,7 @@ var g$4 = t$60()({
6089
6104
  });
6090
6105
  var n$36 = t$60()({
6091
6106
  type: "layoutContainer",
6092
- attributeValues: { subType: e$39()([
6107
+ attributeValues: { subType: e$40()([
6093
6108
  "div",
6094
6109
  "header",
6095
6110
  "footer",
@@ -6221,6 +6236,11 @@ var t$49 = t$60()({
6221
6236
  });
6222
6237
  var t$48 = t$60()({
6223
6238
  type: "page",
6239
+ serverTemplates: [
6240
+ "seoPageTitle",
6241
+ "seoPageDescription",
6242
+ "notFound"
6243
+ ],
6224
6244
  triggers: { onPageLoad: {
6225
6245
  action: "onPageLoad",
6226
6246
  title: "On Page Load",
@@ -6388,12 +6408,12 @@ var t$46 = t$60()({
6388
6408
  var n$35 = t$60()({
6389
6409
  type: "image",
6390
6410
  attributeValues: {
6391
- fetchPriority: e$39()([
6411
+ fetchPriority: e$40()([
6392
6412
  "high",
6393
6413
  "low",
6394
6414
  "auto"
6395
6415
  ]),
6396
- loadMode: e$39()([
6416
+ loadMode: e$40()([
6397
6417
  "auto",
6398
6418
  "eager",
6399
6419
  "lazy"
@@ -6542,7 +6562,7 @@ var t$44 = t$60()({
6542
6562
  var n$34 = t$60()({
6543
6563
  type: "apiContainer",
6544
6564
  attributeValues: {
6545
- subType: e$39()([
6565
+ subType: e$40()([
6546
6566
  "",
6547
6567
  "div",
6548
6568
  "header",
@@ -6555,25 +6575,26 @@ var n$34 = t$60()({
6555
6575
  "address",
6556
6576
  "figure"
6557
6577
  ]),
6558
- method: e$39()([
6578
+ method: e$40()([
6559
6579
  "get",
6560
6580
  "post",
6561
6581
  "put",
6562
6582
  "delete",
6563
6583
  "patch"
6564
6584
  ]),
6565
- credentials: e$39()([
6585
+ credentials: e$40()([
6566
6586
  "include",
6567
6587
  "omit",
6568
6588
  "same-origin"
6569
6589
  ]),
6570
- pagination: e$39()([
6590
+ pagination: e$40()([
6571
6591
  "none",
6572
6592
  "url",
6573
6593
  "append"
6574
6594
  ])
6575
6595
  },
6576
6596
  sourceType: "apiContainer",
6597
+ serverTemplates: ["notFound"],
6577
6598
  triggers: {
6578
6599
  onApiError: {
6579
6600
  action: "onApiError",
@@ -6715,7 +6736,7 @@ var n$34 = t$60()({
6715
6736
  });
6716
6737
  var n$33 = t$60()({
6717
6738
  type: "channel",
6718
- attributeValues: { subType: e$39()([
6739
+ attributeValues: { subType: e$40()([
6719
6740
  "",
6720
6741
  "div",
6721
6742
  "section",
@@ -6734,7 +6755,8 @@ var n$33 = t$60()({
6734
6755
  data: "",
6735
6756
  from: "",
6736
6757
  user: "",
6737
- at: ""
6758
+ at: "",
6759
+ echo: !1
6738
6760
  }
6739
6761
  },
6740
6762
  onJoin: {
@@ -6776,6 +6798,11 @@ var n$33 = t$60()({
6776
6798
  label: "Data (JSON or a template)",
6777
6799
  defaultValue: "",
6778
6800
  type: "text"
6801
+ },
6802
+ echo: {
6803
+ label: "Heard on this page too",
6804
+ defaultValue: !1,
6805
+ type: "boolean"
6779
6806
  }
6780
6807
  }
6781
6808
  },
@@ -6801,7 +6828,7 @@ var n$33 = t$60()({
6801
6828
  definition: {
6802
6829
  label: "Channel",
6803
6830
  type: "channel",
6804
- description: "A realtime channel: every page on the same `topic` hears what any of them publishes, within milliseconds. The topic must match a channel the space declares in its settings (`channels`), e.g. `board:{{ id }}` under `board:{id}`. A topic of a private channel (`grant: true`) opens only with `grant`, bound to the grant the action that let this visitor in returned (its `realtime.grant` step). ITS DESCENDANTS bind to its source `channel_<id>`: `connected`, `members` (with the state each announced), `messages` (the last `keep`) and `last`. It fires `onMessage`; `onJoin` when somebody who came after this page has announced who they are, and `onLeave` when somebody goes — each with `from`, `user` and the `state` they announced; and answers `publish({ type, data })` and `setPresence({ data })`; `presence` is the state this page announces (a name, a colour). Closed in the builder and anywhere without a server.",
6831
+ description: "A realtime channel: every OTHER page on the same `topic` hears what one publishes, within milliseconds — the page that sent it too with `echo` (its `from` is then the channel's `me`). The topic must match a channel the space declares in its settings (`channels`), e.g. `board:{{ id }}` under `board:{id}`. A topic of a private channel (`grant: true`) opens only with `grant`, bound to the grant the action that let this visitor in returned (its `realtime.grant` step). ITS DESCENDANTS bind to its source `channel_<id>`: `connected`, `members` (with the state each announced), `messages` (the last `keep`) and `last`. It fires `onMessage`; `onJoin` when somebody who came after this page has announced who they are, and `onLeave` when somebody goes — each with `from`, `user` and the `state` they announced; and answers `publish({ type, data, echo })` and `setPresence({ data })`; `presence` is the state this page announces (a name, a colour). Closed in the builder and anywhere without a server.",
6805
6832
  items: [],
6806
6833
  bindings: {},
6807
6834
  styleSelectors: { base: "" },
@@ -6835,6 +6862,49 @@ var n$33 = t$60()({
6835
6862
  }
6836
6863
  });
6837
6864
  var t$43 = t$60()({
6865
+ type: "carouselTrack",
6866
+ ancestorType: "carousel",
6867
+ content: {
6868
+ attributes: {},
6869
+ definition: {
6870
+ label: "Carousel Track",
6871
+ type: "carouselTrack",
6872
+ description: "Where a carousel shows its slides: what is inside is one slide, rendered once per item.",
6873
+ items: [],
6874
+ bindings: {},
6875
+ styleSelectors: { base: "" },
6876
+ initialState: { visibility: !0 }
6877
+ },
6878
+ builder: {
6879
+ canDelete: !0,
6880
+ canSelect: !0,
6881
+ canDragDrop: !0,
6882
+ canMove: !0,
6883
+ canSnippet: !1,
6884
+ itemsAllowed: [],
6885
+ itemsNotAllowed: []
6886
+ },
6887
+ market: {
6888
+ category: "structure",
6889
+ owner: "Plitzi",
6890
+ verified: !0,
6891
+ license: "MIT",
6892
+ website: "https://plitzi.com",
6893
+ backgroundColor: "#4422ee",
6894
+ icon: "fa-solid fa-film"
6895
+ },
6896
+ defaultStyle: {
6897
+ name: "Carousel Track",
6898
+ displayMode: "desktop",
6899
+ style: { base: { default: {
6900
+ position: "relative",
6901
+ overflow: "hidden"
6902
+ } } }
6903
+ },
6904
+ settings: {}
6905
+ }
6906
+ });
6907
+ var t$42 = t$60()({
6838
6908
  type: "carousel",
6839
6909
  sourceType: "carousel",
6840
6910
  triggers: { onChange: {
@@ -6936,53 +7006,10 @@ var t$43 = t$60()({
6936
7006
  },
6937
7007
  initialItems: ["carouselTrack"]
6938
7008
  });
6939
- var t$42 = t$60()({
6940
- type: "carouselTrack",
6941
- ancestorType: "carousel",
6942
- content: {
6943
- attributes: {},
6944
- definition: {
6945
- label: "Carousel Track",
6946
- type: "carouselTrack",
6947
- description: "Where a carousel shows its slides: what is inside is one slide, rendered once per item.",
6948
- items: [],
6949
- bindings: {},
6950
- styleSelectors: { base: "" },
6951
- initialState: { visibility: !0 }
6952
- },
6953
- builder: {
6954
- canDelete: !0,
6955
- canSelect: !0,
6956
- canDragDrop: !0,
6957
- canMove: !0,
6958
- canSnippet: !1,
6959
- itemsAllowed: [],
6960
- itemsNotAllowed: []
6961
- },
6962
- market: {
6963
- category: "structure",
6964
- owner: "Plitzi",
6965
- verified: !0,
6966
- license: "MIT",
6967
- website: "https://plitzi.com",
6968
- backgroundColor: "#4422ee",
6969
- icon: "fa-solid fa-film"
6970
- },
6971
- defaultStyle: {
6972
- name: "Carousel Track",
6973
- displayMode: "desktop",
6974
- style: { base: { default: {
6975
- position: "relative",
6976
- overflow: "hidden"
6977
- } } }
6978
- },
6979
- settings: {}
6980
- }
6981
- });
6982
7009
  var r$30 = t$60()({
6983
7010
  type: "container",
6984
7011
  attributeValues: {
6985
- subType: e$39()([
7012
+ subType: e$40()([
6986
7013
  "div",
6987
7014
  "header",
6988
7015
  "footer",
@@ -7006,7 +7033,7 @@ var r$30 = t$60()({
7006
7033
  "p",
7007
7034
  "span"
7008
7035
  ]),
7009
- live: e$39()([
7036
+ live: e$40()([
7010
7037
  "",
7011
7038
  "polite",
7012
7039
  "assertive"
@@ -7097,8 +7124,8 @@ var B$2 = {
7097
7124
  BlockHtml: t$59,
7098
7125
  BlockJsx: t$58,
7099
7126
  Button: r$33,
7100
- Carousel: t$43,
7101
- CarouselTrack: t$42,
7127
+ Carousel: t$42,
7128
+ CarouselTrack: t$43,
7102
7129
  Channel: n$33,
7103
7130
  Container: r$30,
7104
7131
  Custom: t$57,
@@ -7231,8 +7258,7 @@ var B$2 = {
7231
7258
  top: "0",
7232
7259
  position: "absolute",
7233
7260
  "z-index": "210",
7234
- "background-color": "black",
7235
- opacity: "0.5"
7261
+ "background-color": "rgb(0 0 0 / 50%)"
7236
7262
  } },
7237
7263
  rootContainer: { default: {
7238
7264
  display: "flex",
@@ -7303,8 +7329,8 @@ var B$2 = {
7303
7329
  List: t$60()({
7304
7330
  type: "list",
7305
7331
  attributeValues: {
7306
- subType: e$39()(["ul", "ol"]),
7307
- source: e$39()(["none", "controlled"])
7332
+ subType: e$40()(["ul", "ol"]),
7333
+ source: e$40()(["none", "controlled"])
7308
7334
  },
7309
7335
  sourceType: "list",
7310
7336
  content: {
@@ -7467,7 +7493,7 @@ var B$2 = {
7467
7493
  definition: {
7468
7494
  label: "Modal Container",
7469
7495
  type: "modalContainer",
7470
- description: "A modal overlay container opened and closed through interactions; use for dialogs over the page.",
7496
+ description: "A modal overlay container opened and closed through interactions; use for dialogs over the page. The element itself is fixed over the whole viewport, and holds two layers side by side — neither inside the other: `backgroundContainer`, the dim layer, `rgb(0 0 0 / 50%)` (its `background-color` is the whole dim), and `rootContainer`, the dialog, centred by `top: 50%`, `left: 50%` and `transform: translate3d(-50%, -50%, 0)` — to sit it near the top, set `top` and `transform: translateX(-50%)`; flex alignment on the dim layer moves nothing. Inside the dialog, top to bottom: `headerContainer` (with `headerTitle` and `headerCloseButton`) and `bodyContainer`.",
7471
7497
  items: [],
7472
7498
  bindings: {},
7473
7499
  loadStrategy: "lazy",
@@ -7519,8 +7545,7 @@ var B$2 = {
7519
7545
  top: "0",
7520
7546
  position: "absolute",
7521
7547
  "z-index": "210",
7522
- "background-color": "black",
7523
- opacity: "0.5"
7548
+ "background-color": "rgb(0 0 0 / 50%)"
7524
7549
  } },
7525
7550
  rootContainer: { default: {
7526
7551
  display: "flex",
@@ -7604,8 +7629,8 @@ var B$2 = {
7604
7629
  Pagination: t$60()({
7605
7630
  type: "pagination",
7606
7631
  attributeValues: {
7607
- mode: e$39()(["pages", "loadMore"]),
7608
- target: e$39()(["url", "interaction"])
7632
+ mode: e$40()(["pages", "loadMore"]),
7633
+ target: e$40()(["url", "interaction"])
7609
7634
  },
7610
7635
  triggers: { onPageChange: {
7611
7636
  action: "onPageChange",
@@ -7857,6 +7882,7 @@ var B$2 = {
7857
7882
  ThemeToggle: r$32,
7858
7883
  Video: t$44
7859
7884
  };
7885
+ Object.fromEntries(Object.values(B$2).flatMap((e) => "serverTemplates" in e ? [[e.type, e.serverTemplates]] : []));
7860
7886
  /** Every declaration, read through the part of its shape this catalog uses. */
7861
7887
  var declarations$1 = B$2;
7862
7888
  /** Keyed by the schema `type`, which is what a document and every catalog address a type by. */
@@ -7867,7 +7893,7 @@ var elementCatalog = Object.fromEntries(Object.values(declarations$1).map((decla
7867
7893
  }]));
7868
7894
  Object.keys(elementCatalog);
7869
7895
  Object.fromEntries(Object.values(declarations$1).filter((declaration) => declaration.sourceType).map((declaration) => [declaration.type, declaration.sourceType]));
7870
- Object.fromEntries(Object.values(declarations$1).map((declaration) => [declaration.type, [...Object.keys(e$40), ...Object.keys(declaration.triggers ?? {})]]));
7896
+ Object.fromEntries(Object.values(declarations$1).map((declaration) => [declaration.type, [...Object.keys(e$41), ...Object.keys(declaration.triggers ?? {})]]));
7871
7897
  /** The triggers only some types fire, by action name — for a step builder that wants the title the builder shows. */
7872
7898
  var typeTriggerDefinitions = Object.fromEntries(Object.values(declarations$1).flatMap((declaration) => Object.entries(declaration.triggers ?? {})));
7873
7899
  Object.fromEntries(Object.values(declarations$1).flatMap((declaration) => Object.entries(declaration.callbacks ?? {})));
@@ -7968,10 +7994,20 @@ var AUTHORING_CODES = {
7968
7994
  means: "a page that sends visitors it is not for to a page the space does not have",
7969
7995
  fix: "a page's id or slug (`''` is the home page), or a full URL"
7970
7996
  },
7997
+ "page-template": {
7998
+ kind: "refused",
7999
+ means: "a template in a page's `seoTitle`, `seoDescription` or `notFound` that reads what is not there when the server answers — anything but the page's `runtime: 'server'` providers and `navigation` — or cannot be read",
8000
+ fix: "read the record from a provider with `runtime: 'server'` on the page or its layout — `'{{ apiContainer_post.title }} — Blog'` — or words of its own"
8001
+ },
8002
+ "declared-callback-target": {
8003
+ kind: "refused",
8004
+ means: "a `declaredCallback` target with a key beside `on` and `params` — read nowhere, so the step runs without it",
8005
+ fix: "the value in `params`: `{ on: 'calendar', params: { date } }`"
8006
+ },
7971
8007
  "folder-undeclared": {
7972
8008
  kind: "refused",
7973
8009
  means: "a page, a layout or a folder filed in a folder the space does not declare",
7974
- fix: "declare the folder, or the name it suggests"
8010
+ fix: "declare it on the space — `pageFolders: [{ id: 'docs', name: 'Docs' }]`, its slug starting its pages' addresses — or use the name it suggests"
7975
8011
  },
7976
8012
  "folder-cycle": {
7977
8013
  kind: "refused",
@@ -8068,6 +8104,11 @@ var AUTHORING_CODES = {
8068
8104
  means: "a `slots` key the element type does not have — its class would dress nothing",
8069
8105
  fix: "one of the slots `plitzi explain <type>` lists; the element itself takes `class`"
8070
8106
  },
8107
+ "slot-not-class": {
8108
+ kind: "refused",
8109
+ means: "a `slots` entry that is not a class — rules written in place, or nothing — which a slot cannot wear",
8110
+ fix: "the rules as a `styles()` declaration, handed to the slot beside its other classes"
8111
+ },
8071
8112
  "class-conflict": {
8072
8113
  kind: "refused",
8073
8114
  means: "two different declarations for one class name",
@@ -8081,7 +8122,7 @@ var AUTHORING_CODES = {
8081
8122
  "class-and-css": {
8082
8123
  kind: "refused",
8083
8124
  means: "an element wearing a shared class AND `css` or `states` of its own — it has one base selector",
8084
- fix: "the rules on top of the class: `class: [card, { opacity: '0.5' }]` (needs an `id`) — or into the class"
8125
+ fix: "the rules on top of the class: `class: [card, { opacity: '0.5' }]` — or into the class"
8085
8126
  },
8086
8127
  "class-and-selector": {
8087
8128
  kind: "refused",
@@ -8093,11 +8134,6 @@ var AUTHORING_CODES = {
8093
8134
  means: "more than one set of rules in a class list",
8094
8135
  fix: "one set, after the classes: `class: [card, { opacity: '0.5', 'margin-top': '8px' }]`"
8095
8136
  },
8096
- "modifier-without-id": {
8097
- kind: "refused",
8098
- means: "rules on top of a class on an element with no `id` — the id names the class they become",
8099
- fix: "`id: 'hero-bg'`"
8100
- },
8101
8137
  "selector-invalid": {
8102
8138
  kind: "refused",
8103
8139
  means: "a `selector` that is not a CSS class name",
@@ -8143,6 +8179,11 @@ var AUTHORING_CODES = {
8143
8179
  means: "an ancestor condition keyed by something that is neither a class name nor `'>'` (the parent)",
8144
8180
  fix: "`[card.name]` for a `styles()` declaration, or `'>'` for the parent whatever it wears"
8145
8181
  },
8182
+ "focus-on-field-box": {
8183
+ kind: "warned",
8184
+ means: "a `focus` or `focus-visible` state on a text field's or a select's `input` slot — the box the field is drawn in, which never takes focus, so no ring is ever shown",
8185
+ fix: "`'focus-within'` on the same class, or the state on the `field` slot"
8186
+ },
8146
8187
  "heading-level-overridden": {
8147
8188
  kind: "warned",
8148
8189
  means: "a markdown's or richText's `heading` slot and a level's (`heading3`) both setting one property, the general class written later in the stylesheet — the level's value is lost",
@@ -8373,6 +8414,11 @@ var AUTHORING_CODES = {
8373
8414
  means: "a `runtime: 'server'` element inside a component: the page server resolves a page's and its layouts', never a component's",
8374
8415
  fix: "put the provider on the page, around the instance, and hand the component its rows as a prop"
8375
8416
  },
8417
+ "template-in-value": {
8418
+ kind: "refused",
8419
+ means: "a `{{ }}` inside an attribute that is an object or a list — a provider's `input`, a list's `items`, a plugin's settings: only an attribute that is text is evaluated",
8420
+ fix: "a binding on the attribute (`bindTemplate(name, source, template, { returns: 'value' })`); for a provider's `input`, nothing for a route or query param — the action is already handed them as `input.<name>`"
8421
+ },
8376
8422
  "not-found-in-browser": {
8377
8423
  kind: "refused",
8378
8424
  means: "`notFound` on a provider asked from the browser — its answer arrives after the page was sent with its status",
@@ -8380,7 +8426,7 @@ var AUTHORING_CODES = {
8380
8426
  },
8381
8427
  "not-found-not-a-template": {
8382
8428
  kind: "refused",
8383
- means: "`notFound` that is not one `{{ expression }}` against the answer — never `true`, so never a 404",
8429
+ means: "`notFound`, a provider's or a page's, that is not one `{{ expression }}` — never `true`, so never a 404",
8384
8430
  fix: "`notFound: '{{ source.found == false }}'`"
8385
8431
  },
8386
8432
  "server-data-in-browser": {
@@ -8403,6 +8449,11 @@ var AUTHORING_CODES = {
8403
8449
  means: "a control in a form with no `name` — its value never reaches `values`",
8404
8450
  fix: "`formControl({ name: 'email', … })`"
8405
8451
  },
8452
+ "required-message-unused": {
8453
+ kind: "warned",
8454
+ means: "a `requiredMessage` on a field nothing requires — fields are optional unless `required: true`, so it is never shown and an empty answer is sent",
8455
+ fix: "`required: true` on the field, or no message"
8456
+ },
8406
8457
  "form-control-name-taken": {
8407
8458
  kind: "warned",
8408
8459
  means: "two controls in one form with one name — one overwrites the other",
@@ -8980,10 +9031,14 @@ var sourceRow = (listSource, items) => {
8980
9031
  const shape = Reflect.get(items, SHAPE);
8981
9032
  return {
8982
9033
  item: pathTo(`${listSource}.item`, Array.isArray(shape) ? itemShape(shape) : UNKNOWN),
8983
- index: pathTo(`${listSource}.index`, 0)
9034
+ index: pathTo(`${listSource}.index`, 0),
9035
+ inTemplate: {
9036
+ item: `${listSource}.item`,
9037
+ index: `${listSource}.index`
9038
+ }
8984
9039
  };
8985
9040
  };
8986
- var e$38 = [
9041
+ var e$39 = [
8987
9042
  "variables",
8988
9043
  "navigation",
8989
9044
  "auth",
@@ -8993,7 +9048,7 @@ var e$38 = [
8993
9048
  "flags",
8994
9049
  "computed"
8995
9050
  ];
8996
- e$38.filter((e) => e !== "state" && e !== "computed");
9051
+ e$39.filter((e) => e !== "state" && e !== "computed");
8997
9052
  var t$30 = {
8998
9053
  DISPLAY: "display",
8999
9054
  FLEX_WRAP: "flex-wrap",
@@ -9199,7 +9254,7 @@ Object.values(t$30).map((e) => ({
9199
9254
  label: e
9200
9255
  }));
9201
9256
  t$30.DISPLAY, t$30.FLEX_WRAP, t$30.FLEX_DIRECTION, t$30.ALIGN_ITEMS, t$30.JUSTIFY_ITEMS, t$30.JUSTIFY_CONTENT, t$30.ALIGN_CONTENT, t$30.ROW_GAP, t$30.COLUMN_GAP, t$30.GRID_ROW_GAP, t$30.GRID_COLUMN_GAP, t$30.GRID_TEMPLATE_AREAS, t$30.GRID_TEMPLATE_COLUMNS, t$30.GRID_TEMPLATE_ROWS, t$30.GRID_AUTO_FLOW, t$30.GRID_AUTO_ROWS, t$30.GRID_AUTO_COLUMNS, t$30.GRID_ROW_START, t$30.GRID_COLUMN_START, t$30.GRID_ROW_END, t$30.GRID_COLUMN_END, t$30.ALIGN_SELF, t$30.JUSTIFY_SELF, t$30.ORDER, t$30.FLEX_GROW, t$30.FLEX_SHRINK, t$30.FLEX_BASIS, t$30.LIST_STYLE, t$30.LIST_STYLE_TYPE, t$30.LIST_STYLE_POSITION, t$30.LIST_STYLE_IMAGE, t$30.BACKGROUND_COLOR, t$30.BACKGROUND_IMAGE, t$30.BACKGROUND_POSITION, t$30.BACKGROUND_SIZE, t$30.BACKGROUND_CLIP, t$30.BACKGROUND_ORIGIN, t$30.BACKGROUND_REPEAT, t$30.BACKGROUND_ATTACHMENT, t$30.BACKGROUND_BLEND_MODE, t$30.MASK_IMAGE, t$30.MASK_SIZE, t$30.MASK_POSITION, t$30.MASK_REPEAT, t$30.MASK_COMPOSITE, t$30.WEBKIT_MASK_IMAGE, t$30.BORDER_TOP_STYLE, t$30.BORDER_TOP_WIDTH, t$30.BORDER_TOP_COLOR, t$30.BORDER_BOTTOM_STYLE, t$30.BORDER_BOTTOM_WIDTH, t$30.BORDER_BOTTOM_COLOR, t$30.BORDER_LEFT_STYLE, t$30.BORDER_LEFT_WIDTH, t$30.BORDER_LEFT_COLOR, t$30.BORDER_RIGHT_STYLE, t$30.BORDER_RIGHT_WIDTH, t$30.BORDER_RIGHT_COLOR, t$30.BORDER_TOP_LEFT_RADIUS, t$30.BORDER_TOP_RIGHT_RADIUS, t$30.BORDER_BOTTOM_LEFT_RADIUS, t$30.BORDER_BOTTOM_RIGHT_RADIUS, t$30.BORDER_COLLAPSE, t$30.BORDER_SPACING, t$30.TABLE_LAYOUT, t$30.OPACITY, t$30.VISIBILITY, t$30.CURSOR, t$30.TRANSITION, t$30.TRANSITION_PROPERTY, t$30.TRANSITION_DURATION, t$30.TRANSITION_TIMING_FUNCTION, t$30.TRANSITION_DELAY, t$30.TRANSITION_BEHAVIOR, t$30.ANIMATION, t$30.ANIMATION_NAME, t$30.ANIMATION_DURATION, t$30.ANIMATION_TIMING_FUNCTION, t$30.ANIMATION_DELAY, t$30.ANIMATION_ITERATION_COUNT, t$30.ANIMATION_DIRECTION, t$30.ANIMATION_FILL_MODE, t$30.ANIMATION_PLAY_STATE, t$30.ANIMATION_TIMELINE, t$30.ANIMATION_RANGE, t$30.BOX_SHADOW, t$30.FILTER, t$30.BACKDROP_FILTER, t$30.MIX_BLEND_MODE, t$30.ISOLATION, t$30.CLIP_PATH, t$30.TRANSFORM, t$30.TRANSFORM_ORIGIN, t$30.TRANSLATE, t$30.ROTATE, t$30.SCALE, t$30.TRANSFORM_STYLE, t$30.PERSPECTIVE, t$30.PERSPECTIVE_ORIGIN, t$30.BACKFACE_VISIBILITY, t$30.WILL_CHANGE, t$30.OUTLINE_WIDTH, t$30.OUTLINE_STYLE, t$30.OUTLINE_COLOR, t$30.OUTLINE_OFFSET, t$30.POINTER_EVENTS, t$30.USER_SELECT, t$30.TOUCH_ACTION, t$30.RESIZE, t$30.APPEARANCE, t$30.SCROLL_BEHAVIOR, t$30.OVERSCROLL_BEHAVIOR, t$30.SCROLL_SNAP_TYPE, t$30.SCROLL_SNAP_ALIGN, t$30.SCROLL_SNAP_STOP, t$30.SCROLL_PADDING_TOP, t$30.SCROLL_MARGIN_TOP, t$30.ACCENT_COLOR, t$30.CARET_COLOR, t$30.COLOR_SCHEME, t$30.POSITION, t$30.FLOAT, t$30.CLEAR, t$30.Z_INDEX, t$30.TOP, t$30.BOTTOM, t$30.LEFT, t$30.RIGHT, t$30.BOX_SIZING, t$30.ASPECT_RATIO, t$30.WIDTH, t$30.HEIGHT, t$30.MIN_WIDTH, t$30.MIN_HEIGHT, t$30.MAX_WIDTH, t$30.MAX_HEIGHT, t$30.OVERFLOW, t$30.OVERFLOW_X, t$30.OVERFLOW_Y, t$30.SCROLLBAR_WIDTH, t$30.SCROLLBAR_COLOR, t$30.CONTAINER_TYPE, t$30.CONTENT, t$30.CONTAINER_NAME, t$30.OBJECT_FIT, t$30.OBJECT_POSITION, t$30.COLUMN_WIDTH, t$30.COLUMN_COUNT, t$30.MARGIN_TOP, t$30.MARGIN_BOTTOM, t$30.MARGIN_LEFT, t$30.MARGIN_RIGHT, t$30.PADDING_TOP, t$30.PADDING_BOTTOM, t$30.PADDING_LEFT, t$30.PADDING_RIGHT, t$30.FONT_FAMILY, t$30.FONT_WEIGHT, t$30.FONT_SIZE, t$30.LINE_HEIGHT, t$30.COLOR, t$30.TEXT_ALIGN, t$30.FONT_STYLE, t$30.FONT_VARIANT, t$30.FONT_VARIANT_NUMERIC, t$30.FONT_FEATURE_SETTINGS, t$30.TEXT_DECORATION, t$30.TEXT_DECORATION_LINE, t$30.TEXT_DECORATION_COLOR, t$30.TEXT_DECORATION_STYLE, t$30.TEXT_DECORATION_THICKNESS, t$30.TEXT_UNDERLINE_OFFSET, t$30.LETTER_SPACING, t$30.WORD_SPACING, t$30.TEXT_INDENT, t$30.TEXT_TRANSFORM, t$30.DIRECTION, t$30.WRITING_MODE, t$30.TEXT_ORIENTATION, t$30.WHITE_SPACE, t$30.TEXT_WRAP, t$30.WORD_BREAK, t$30.OVERFLOW_WRAP, t$30.HYPHENS, t$30.VERTICAL_ALIGN, t$30.TEXT_OVERFLOW, t$30.TEXT_SHADOW, t$30.LINE_CLAMP, t$30.WEBKIT_LINE_CLAMP, t$30.WEBKIT_BOX_ORIENT, t$30.WEBKIT_TEXT_FILL_COLOR, t$30.WEBKIT_BACKGROUND_CLIP, t$30.BOX_DECORATION_BREAK, t$30.WEBKIT_BOX_DECORATION_BREAK, t$30.FILL, t$30.STROKE, t$30.STROKE_WIDTH;
9202
- [...e$38];
9257
+ [...e$39];
9203
9258
  /** Both binding forms as one list. The map form targets attributes, which is what nearly every binding does. */
9204
9259
  var toBindingSpecs = (bind) => Array.isArray(bind) ? bind : Object.entries(bind).map(([to, source]) => ({
9205
9260
  to,
@@ -9511,20 +9566,20 @@ var tabContainerItem = defineElement(B$2.TabContainerItem);
9511
9566
  var text$1 = defineElement(B$2.Text);
9512
9567
  var themeToggle = defineElement(B$2.ThemeToggle);
9513
9568
  var video = defineElement(B$2.Video);
9514
- var e$32 = (e) => typeof e == "object" && !!e && !Array.isArray(e);
9569
+ var e$33 = (e) => typeof e == "object" && !!e && !Array.isArray(e);
9515
9570
  var STEP_TYPES = [
9516
9571
  "trigger",
9517
9572
  "globalCallback",
9518
9573
  "callback",
9519
9574
  "utility"
9520
9575
  ];
9521
- var e$30 = {
9576
+ var e$31 = {
9522
9577
  desktop: 1024,
9523
9578
  tablet: 768,
9524
9579
  mobile: 0
9525
9580
  };
9526
- var t$26 = (t) => e$30[t] === 0 ? "0" : `${String(e$30[t] / 16)}rem`;
9527
- var e$29 = [
9581
+ var t$26 = (t) => e$31[t] === 0 ? "0" : `${String(e$31[t] / 16)}rem`;
9582
+ var e$30 = [
9528
9583
  "desktop",
9529
9584
  "tablet",
9530
9585
  "mobile"
@@ -9532,14 +9587,14 @@ var e$29 = [
9532
9587
  Object.values(/* @__PURE__ */ function(e) {
9533
9588
  return e.COLOR = "color", e.SPACING = "spacing", e.SHADOW = "shadow", e.CUSTOM = "custom", e;
9534
9589
  }({}));
9535
- var e$27 = {
9590
+ var e$28 = {
9536
9591
  "motion-reduce": "@media (prefers-reduced-motion: reduce)",
9537
9592
  "motion-safe": "@media (prefers-reduced-motion: no-preference)"
9538
9593
  };
9539
- var t$23 = Object.keys(e$27);
9540
- var r$18 = (t) => Object.hasOwn(e$27, t);
9541
- var i$17 = String.raw`\d+(?:\.\d+)?(?:px|rem|em|ch)`;
9542
- var a$15 = String.raw`\((?:min|max)-width:\s*${i$17}\)`;
9594
+ var t$23 = Object.keys(e$28);
9595
+ var r$18 = (t) => Object.hasOwn(e$28, t);
9596
+ var i$18 = String.raw`\d+(?:\.\d+)?(?:px|rem|em|ch)`;
9597
+ var a$15 = String.raw`\((?:min|max)-width:\s*${i$18}\)`;
9543
9598
  var o$17 = new RegExp(String.raw`^container(?:\s+(?<name>-?[_a-zA-Z][\w-]*))?\s+(?<query>${a$15}(?:\s+and\s+${a$15})?)$`);
9544
9599
  var s$15 = (e) => {
9545
9600
  let t = o$17.exec(e.trim());
@@ -9553,7 +9608,7 @@ var c$12 = (e) => {
9553
9608
  let t = s$15(e);
9554
9609
  if (t) return t.name ? `container ${t.name} ${t.query}` : `container ${t.query}`;
9555
9610
  };
9556
- var e$26 = [
9611
+ var e$27 = [
9557
9612
  "before",
9558
9613
  "after",
9559
9614
  "marker",
@@ -9572,7 +9627,7 @@ var t$22 = {
9572
9627
  selection: "::selection"
9573
9628
  };
9574
9629
  var n$19 = (e) => Object.hasOwn(t$22, e);
9575
- var i$16 = [
9630
+ var i$17 = [
9576
9631
  "before",
9577
9632
  "after",
9578
9633
  "marker"
@@ -9633,7 +9688,7 @@ var l$8 = {
9633
9688
  ...c$11
9634
9689
  ]
9635
9690
  };
9636
- var u$7 = (e, t) => {
9691
+ var u$8 = (e, t) => {
9637
9692
  let n = l$8[e];
9638
9693
  return !n || t.startsWith("--") || n.includes(t);
9639
9694
  };
@@ -9672,7 +9727,7 @@ var m$4 = (e) => {
9672
9727
  let t = p$8(e.trim());
9673
9728
  return t?.length ? t.every((e) => /^(["']).*\1$/s.test(e) || d$7.has(e.toLowerCase()) || f$5.test(e) && e.endsWith(")")) : !1;
9674
9729
  };
9675
- var e$25 = [
9730
+ var e$26 = [
9676
9731
  "hover",
9677
9732
  "focus",
9678
9733
  "focus-visible",
@@ -9707,7 +9762,7 @@ var r$16 = {
9707
9762
  disabled: 13,
9708
9763
  hidden: 14
9709
9764
  };
9710
- var i$15 = (e) => Object.hasOwn(r$16, e);
9765
+ var i$16 = (e) => Object.hasOwn(r$16, e);
9711
9766
  var l$7 = {
9712
9767
  current: ":is([aria-current]:not([aria-current=\"false\"]),[aria-pressed=\"true\"],[aria-selected=\"true\"],[role=\"tabpanel\"]:not([hidden]))",
9713
9768
  expanded: "[aria-expanded=\"true\"]",
@@ -9717,7 +9772,7 @@ var l$7 = {
9717
9772
  odd: ":nth-child(odd)",
9718
9773
  even: ":nth-child(even)"
9719
9774
  };
9720
- var u$6 = (e) => (i$15(e) ? l$7[e] : void 0) ?? `:${e}`;
9775
+ var u$7 = (e) => (i$16(e) ? l$7[e] : void 0) ?? `:${e}`;
9721
9776
  var p$7 = (e) => e === ">";
9722
9777
  var n$16 = (e) => {
9723
9778
  let t = typeof e == "string" && e.trim() !== "" ? Number(e) : e;
@@ -9753,7 +9808,7 @@ var r$13 = {
9753
9808
  del: "delete",
9754
9809
  return: "enter"
9755
9810
  };
9756
- var i$12 = /* @__PURE__ */ new Set([
9811
+ var i$13 = /* @__PURE__ */ new Set([
9757
9812
  "escape",
9758
9813
  "enter",
9759
9814
  "tab",
@@ -9786,7 +9841,7 @@ var o$11 = (e, t) => {
9786
9841
  }
9787
9842
  if (u.length !== 1) return { problem: `"${e.trim()}" names ${u.length ? `${u.length} keys (${u.join(", ")})` : "no key, only modifiers"}: a shortcut is modifiers and ONE key, like "shift+f"` };
9788
9843
  let [d] = u;
9789
- return d.length !== 1 && !i$12.has(d) ? { problem: `"${d}" in "${e.trim()}" is not a key: use one character ("f", "?", "+") or a key's name (${[...i$12].slice(0, 15).join(", ")}, f1…f12)` } : { combo: a$10(l, d) };
9844
+ return d.length !== 1 && !i$13.has(d) ? { problem: `"${d}" in "${e.trim()}" is not a key: use one character ("f", "?", "+") or a key's name (${[...i$13].slice(0, 15).join(", ")}, f1…f12)` } : { combo: a$10(l, d) };
9790
9845
  };
9791
9846
  var s$9 = (e, t = !1) => {
9792
9847
  let n = e.split(",").map((e) => o$11(e, t));
@@ -9839,7 +9894,7 @@ var steps_exports$1 = /* @__PURE__ */ __exportAll({
9839
9894
  * the builders useless for exactly the flows that matter most.
9840
9895
  */
9841
9896
  var builtinTrigger = (trigger) => {
9842
- if (Object.hasOwn(e$40, trigger)) return e$40[trigger];
9897
+ if (Object.hasOwn(e$41, trigger)) return e$41[trigger];
9843
9898
  return Object.hasOwn(typeTriggerDefinitions, trigger) ? typeTriggerDefinitions[trigger] : void 0;
9844
9899
  };
9845
9900
  var on = (trigger, params = {}) => {
@@ -10004,19 +10059,22 @@ var openModal = (target, metadata) => ({
10004
10059
  params: metadata === void 0 ? {} : { metadata }
10005
10060
  });
10006
10061
  /**
10007
- * Says something on a `channel`, by id: every page on its topic hears it — `onMessage`, and the channel's source.
10062
+ * Says something on a `channel`, by id: every OTHER page on its topic hears it — `onMessage`, and the channel's source.
10063
+ * The page that said it hears it too with `{ echo: true }` — a reaction, a chat line: what the sender shows the moment
10064
+ * it sends — once the server took it, its `from` the channel's `me`.
10008
10065
  *
10009
10066
  * `data` is what they receive: a template (`'{{ state.draft }}'`) or JSON text. `type` is yours to name — `chat`,
10010
10067
  * `wave`, `move` — and a flow on `onMessage` tells them apart with `when({ field: '<step>.type', … })`.
10011
10068
  */
10012
- var publishOn = (target, type, data = null) => ({
10069
+ var publishOn = (target, type, data = null, { echo = false } = {}) => ({
10013
10070
  type: "callback",
10014
10071
  action: "publish",
10015
10072
  title: "Publish",
10016
10073
  on: target,
10017
10074
  params: {
10018
10075
  type,
10019
- data: typeof data === "string" ? data : JSON.stringify(data)
10076
+ data: typeof data === "string" ? data : JSON.stringify(data),
10077
+ ...echo ? { echo } : {}
10020
10078
  }
10021
10079
  });
10022
10080
  /** Announces this page on a `channel` with presence — a name, a colour: what the other members see of it. */
@@ -10123,6 +10181,12 @@ var declaredTrigger = (declaration, trigger) => {
10123
10181
  */
10124
10182
  var declaredCallback = (declaration, callback, target) => {
10125
10183
  const declared = declaration.callbacks?.[callback];
10184
+ const stray = Object.keys(target).filter((key) => key !== "on" && key !== "params");
10185
+ if (stray.length > 0) {
10186
+ const named = Object.keys(declared?.params ?? {});
10187
+ const moved = stray.map((key) => `${key}: ${JSON.stringify(Reflect.get(target, key))}`).join(", ");
10188
+ throw new AuthoringError("declared-callback-target", `declaredCallback(…, '${callback}') is given ${stray.map((key) => `"${key}"`).join(", ")} beside \`on\`, where nothing reads it${stray.every((key) => named.includes(key)) ? ` — ${stray.length === 1 ? "it is a param" : "they are params"} of "${callback}"` : ""}: a callback's values go in \`params\` — \`{ on: '${target.on}', params: { ${moved} } }\`.`);
10189
+ }
10126
10190
  return {
10127
10191
  type: "callback",
10128
10192
  action: declared?.action ?? callback,
@@ -10150,7 +10214,7 @@ var carouselGoTo = (target, index) => carouselStep(target, "goTo", "Go To Slide"
10150
10214
  var carouselPlay = (target) => carouselStep(target, "play", "Play");
10151
10215
  /** The carousel `target` stops moving by itself until it is told to play. */
10152
10216
  var carouselPause = (target) => carouselStep(target, "pause", "Pause");
10153
- var e$21 = {
10217
+ var e$22 = {
10154
10218
  origin: "Requests to the same site",
10155
10219
  all: "All cached requests",
10156
10220
  elements: "Only these containers",
@@ -10162,7 +10226,7 @@ var t$16 = (t, n = () => !0) => ({
10162
10226
  description: "What to refresh once the write succeeded, so the page shows the new data: the requests to the same site, every provider on the page, only the api containers named in `invalidateElements`, or none for a step that only reads. Server-driven containers are refreshed too, asking the server past its cache. Containers on screen ask again at once; the rest when they are shown.",
10163
10227
  default: t[0],
10164
10228
  options: [...t],
10165
- optionLabels: Object.fromEntries(t.map((t) => [t, e$21[t]])),
10229
+ optionLabels: Object.fromEntries(t.map((t) => [t, e$22[t]])),
10166
10230
  label: "Refresh cached requests",
10167
10231
  canBind: !1,
10168
10232
  when: n
@@ -10242,7 +10306,7 @@ var t$15 = {
10242
10306
  preview: { cancelled: "" }
10243
10307
  }
10244
10308
  };
10245
- var e$20 = {
10309
+ var e$21 = {
10246
10310
  login: {
10247
10311
  source: "auth",
10248
10312
  title: "Auth Login",
@@ -10328,7 +10392,7 @@ var e$20 = {
10328
10392
  params: {}
10329
10393
  }
10330
10394
  };
10331
- var e$19 = { hostAction: {
10395
+ var e$20 = { hostAction: {
10332
10396
  source: "host",
10333
10397
  title: "Host Action",
10334
10398
  strictParams: !0,
@@ -10345,7 +10409,7 @@ var e$19 = { hostAction: {
10345
10409
  }
10346
10410
  }
10347
10411
  } };
10348
- var e$18 = { navigate: {
10412
+ var e$19 = { navigate: {
10349
10413
  source: "navigation",
10350
10414
  title: "Navigate",
10351
10415
  strictParams: !0,
@@ -10372,7 +10436,7 @@ var e$18 = { navigate: {
10372
10436
  }
10373
10437
  }
10374
10438
  } };
10375
- var e$17 = { invalidateQueries: {
10439
+ var e$18 = { invalidateQueries: {
10376
10440
  source: "queries",
10377
10441
  title: "Invalidate Queries",
10378
10442
  strictParams: !0,
@@ -10605,11 +10669,11 @@ var BUILTIN_GLOBAL_CALLBACKS = {
10605
10669
  default: ""
10606
10670
  } }
10607
10671
  },
10608
- ...e$19,
10609
- ...e$18,
10610
10672
  ...e$20,
10673
+ ...e$19,
10674
+ ...e$21,
10611
10675
  ...t$15,
10612
- ...e$17
10676
+ ...e$18
10613
10677
  };
10614
10678
  var steps_exports = /* @__PURE__ */ __exportAll({
10615
10679
  SET_STATE_TYPES: () => SET_STATE_TYPES,
@@ -10848,549 +10912,98 @@ var t$14 = (e) => {
10848
10912
  var t$13 = "[A-Za-z_][A-Za-z0-9_]*";
10849
10913
  RegExp(`^${t$13}$`);
10850
10914
  RegExp(`^!?${t$13}$`);
10851
- var e$8 = "/data/";
10852
- var e$6 = (e) => typeof e == "string" && /\{\{[\s\S]*?\}\}|\{%[\s\S]*?%\}/.test(e);
10853
- var i$4 = (e) => `value|round(0, '${e}')`;
10854
- i$4("floor"), i$4("ceil");
10855
- /** A container with no tag of its own named: the row `list-row-not-li` makes an `<li>` without asking. */
10856
- var isPlainContainer = (element) => element.definition.type === "container" && (element.attributes.subType === void 0 || element.attributes.subType === "div");
10857
- new Set(STEP_TYPES);
10858
- /** A step's params that name a path under `runtime.state`, by action. */
10859
- var STATE_PATH_PARAMS = {
10860
- setState: ["key"],
10861
- toggleState: ["key"],
10862
- appendState: ["key"],
10863
- removeState: ["key"],
10864
- toggleInState: ["key"],
10865
- clearState: ["key"],
10866
- moveState: ["from", "to"]
10915
+ var e$9 = "/data/";
10916
+ var e$7 = (e) => typeof e == "string" && /\{\{[\s\S]*?\}\}|\{%[\s\S]*?%\}/.test(e);
10917
+ var i$5 = (e) => `value|round(0, '${e}')`;
10918
+ i$5("floor"), i$5("ceil");
10919
+ /** Every kebab-case CSS property Plitzi's style engine understands. Values written to a definition must use these
10920
+ * exact keys — camelCase or unknown keys are rejected. */
10921
+ var cssProperties = Array.from(new Set(Object.values(t$30))).sort();
10922
+ var cssPropertySet = new Set(cssProperties);
10923
+ var isCssProperty = (key) => cssPropertySet.has(key);
10924
+ var toKebab = (key) => key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
10925
+ /**
10926
+ * A property as an author may write it, in the spelling the document keeps: kebab-case already, or camelCase the way a
10927
+ * React style object writes it — `paddingTop` is `padding-top`, `WebkitLineClamp` is `-webkit-line-clamp`. A custom
10928
+ * property (`--brand`) is the author's own spelling and is kept.
10929
+ */
10930
+ var cssPropertyName = (key) => {
10931
+ if (isCustomProperty(key) || !/[A-Z]/.test(key)) return key;
10932
+ return /^(Webkit|Moz|Ms|O)(?=[A-Z])/.exec(key) ? `-${toKebab(key)}` : toKebab(key);
10867
10933
  };
10868
- var t$6 = [
10869
- "fade",
10870
- "fade-up",
10871
- "fade-down",
10872
- "slide-left",
10873
- "slide-right",
10874
- "slide-up",
10934
+ /**
10935
+ * The properties a bare number is a whole value of — a weight, an opacity, a count, a ratio. Anywhere else a number is
10936
+ * a length, and a length without a unit is no value at all: `gap: 16` is `16px`.
10937
+ */
10938
+ var UNITLESS = /* @__PURE__ */ new Set([
10939
+ "opacity",
10940
+ "z-index",
10941
+ "font-weight",
10942
+ "line-height",
10943
+ "flex",
10944
+ "flex-grow",
10945
+ "flex-shrink",
10946
+ "order",
10947
+ "column-count",
10948
+ "aspect-ratio",
10949
+ "orphans",
10950
+ "widows",
10951
+ "zoom",
10875
10952
  "scale",
10876
- "zoom-in",
10877
- "zoom-out",
10878
- "pop",
10879
- "bounce-in",
10880
- "tilt",
10881
- "spin-in"
10882
- ];
10883
- var n$6 = [
10884
- "load",
10885
- "view",
10886
- "scroll"
10887
- ];
10888
- var r$4 = [
10889
- "float",
10890
- "pulse",
10891
- "spin",
10892
- "sway",
10893
- "bounce",
10894
- "wobble",
10895
- "orbit",
10896
- "heartbeat",
10897
- "drift"
10898
- ];
10899
- var o$6 = {
10900
- fade: [{
10901
- at: 0,
10902
- opacity: 0
10903
- }],
10904
- "fade-up": [{
10905
- at: 0,
10906
- opacity: 0,
10907
- translate: "0 24px"
10908
- }],
10909
- "fade-down": [{
10910
- at: 0,
10911
- opacity: 0,
10912
- translate: "0 -24px"
10913
- }],
10914
- "slide-left": [{
10915
- at: 0,
10916
- opacity: 0,
10917
- translate: "32px 0"
10918
- }],
10919
- "slide-right": [{
10920
- at: 0,
10921
- opacity: 0,
10922
- translate: "-32px 0"
10923
- }],
10924
- "slide-up": [{
10925
- at: 0,
10926
- opacity: 0,
10927
- translate: "0 72px"
10928
- }],
10929
- scale: [{
10930
- at: 0,
10931
- opacity: 0,
10932
- scale: "0.94"
10933
- }],
10934
- "zoom-in": [{
10935
- at: 0,
10936
- opacity: 0,
10937
- scale: "0.6"
10938
- }],
10939
- "zoom-out": [{
10940
- at: 0,
10941
- opacity: 0,
10942
- scale: "1.25"
10943
- }],
10944
- pop: [
10945
- {
10946
- at: 0,
10947
- opacity: 0,
10948
- scale: "0.5"
10949
- },
10950
- {
10951
- at: 55,
10952
- opacity: 1,
10953
- scale: "1.08"
10954
- },
10955
- {
10956
- at: 78,
10957
- scale: "0.97"
10958
- }
10959
- ],
10960
- "bounce-in": [
10961
- {
10962
- at: 0,
10963
- opacity: 0,
10964
- translate: "0 -64px"
10965
- },
10966
- {
10967
- at: 55,
10968
- opacity: 1,
10969
- translate: "0 10px"
10970
- },
10971
- {
10972
- at: 75,
10973
- translate: "0 -5px"
10974
- },
10975
- {
10976
- at: 90,
10977
- translate: "0 2px"
10978
- }
10979
- ],
10980
- tilt: [{
10981
- at: 0,
10982
- opacity: 0,
10983
- translate: "0 32px",
10984
- rotate: "-6deg"
10985
- }],
10986
- "spin-in": [{
10987
- at: 0,
10988
- opacity: 0,
10989
- scale: "0.4",
10990
- rotate: "-180deg"
10991
- }]
10992
- };
10993
- Object.fromEntries(t$6.map((e) => {
10994
- let [{ at: t, ...n }] = o$6[e];
10995
- return [e, n];
10996
- }));
10997
- var c$4 = (e, t) => {
10998
- let n = t * 2 * Math.PI, r = Math.round(Math.sin(n) * e * 10) / 10, i = Math.round(-Math.cos(n) * e * 10) / 10;
10999
- return `translate(${String(r)}px, ${String(i)}px)`;
10953
+ "tab-size",
10954
+ "animation-iteration-count",
10955
+ "grid-row-start",
10956
+ "grid-row-end",
10957
+ "grid-column-start",
10958
+ "grid-column-end",
10959
+ "fill-opacity",
10960
+ "stroke-opacity",
10961
+ "stroke-width",
10962
+ "-webkit-line-clamp"
10963
+ ]);
10964
+ /** A number written as a value, as CSS reads it: itself where a bare number is a whole value, pixels otherwise. */
10965
+ var cssNumberValue = (property, value) => UNITLESS.has(property) || isCustomProperty(property) || value === 0 ? value : `${String(value)}px`;
10966
+ /** If a camelCase key maps to a known kebab-case property, return it — used to name the correct key in an error. */
10967
+ var suggestCssProperty = (key) => {
10968
+ const kebab = toKebab(key);
10969
+ return cssPropertySet.has(kebab) ? kebab : void 0;
11000
10970
  };
11001
- Array.from({ length: 9 }, (e, t) => ({
11002
- at: t * 100 / 8,
11003
- transform: c$4(6, t / 8)
11004
- }));
11005
- [
11006
- "[data-motion-loop] { animation-play-state: running, running !important; }",
11007
- "[data-motion-on=\"view\"][data-motion-enter], [data-motion-on=\"view\"][data-motion-stagger] > * { animation-play-state: running, running !important; }",
11008
- "[data-motion-on=\"scroll\"][data-motion-enter], [data-motion-on=\"scroll\"][data-motion-stagger] > * { animation-timeline: auto, auto !important; }"
11009
- ].join("\n");
11010
- Object.entries(AUTHORING_CODES).filter(([, entry]) => entry.kind === "suggested").map(([code]) => code);
11011
- var URL_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
11012
- /** A page target that is really a URL: the one reading of `mailto:`/`https:` a page-mode link can never mean. */
11013
- var isUrlTarget = (target) => typeof target === "string" && !e$6(target) && URL_SCHEME.test(target);
11014
- var isRoot = (element) => !element.definition.parentId;
11015
- var stepsOf$1 = (element) => Object.values(element.definition.interactions ?? {});
11016
10971
  /**
11017
- * A key the element or step does not take, renamed to the one it was a typo of — when that one is free — or dropped:
11018
- * it was never read, so dropping it changes nothing on screen.
10972
+ * A CSS custom property — `--brand`, `--space-4`.
10973
+ *
10974
+ * Not in the vocabulary and deliberately allowed anyway: a custom property is a declaration the browser resolves,
10975
+ * not one the style editor has to offer a control for, and a space that defines its palette in one place and reads
10976
+ * it back with `var(--brand)` is doing the right thing.
11019
10977
  */
11020
- var settleUnknownKeys = (record, known, what, on, report, defaults = {}) => {
11021
- for (const key of Object.keys(record)) {
11022
- if (known.includes(key)) continue;
11023
- const wrapped = record[key];
11024
- if (e$32(wrapped) && Object.keys(wrapped).length > 0 && Object.keys(wrapped).every((inner) => known.includes(inner))) {
11025
- const inners = Object.keys(wrapped);
11026
- if (inners.every((inner) => record[inner] === void 0 || record[inner] === defaults[inner])) {
11027
- Object.assign(record, wrapped);
11028
- Reflect.deleteProperty(record, key);
11029
- report(`Moved ${inners.join(", ")} out of the ${what} "${key}", up to where ${inners.length === 1 ? "it is" : "they are"} read.`, {
11030
- on,
11031
- op: "unwrap",
11032
- key
11033
- });
11034
- }
10978
+ var isCustomProperty = (key) => key.startsWith("--");
10979
+ var walkTopLevel = (value, onChar) => {
10980
+ let depth = 0;
10981
+ let quote = "";
10982
+ for (const ch of value) {
10983
+ if (quote) {
10984
+ onChar(ch, false);
10985
+ if (ch === quote) quote = "";
11035
10986
  continue;
11036
10987
  }
11037
- const meant = closest$1(key, known);
11038
- if (meant !== void 0 && record[meant] === void 0) {
11039
- record[meant] = record[key];
11040
- report(`Renamed the ${what} "${key}" to "${meant}".`, {
11041
- on,
11042
- op: "rename",
11043
- key,
11044
- to: meant
11045
- });
11046
- } else report(`Removed the ${what} "${key}", which nothing reads.`, {
11047
- on,
11048
- op: "remove",
11049
- key
11050
- });
11051
- Reflect.deleteProperty(record, key);
10988
+ if (/["']/.test(ch)) {
10989
+ quote = ch;
10990
+ onChar(ch, false);
10991
+ continue;
10992
+ }
10993
+ if (ch === "(") depth += 1;
10994
+ else if (ch === ")") depth = Math.max(0, depth - 1);
10995
+ onChar(ch, depth === 0);
11052
10996
  }
11053
10997
  };
11054
- /** A flag written as the words `'true'`/`'false'`, stored as the boolean it is read as. */
11055
- var asFlag = (value) => value === "true" ? true : value === "false" ? false : void 0;
11056
- var stepSpec = (catalogs, step) => {
11057
- const vocabulary = catalogs.vocabulary;
11058
- const table = step.type === "globalCallback" ? vocabulary?.globalCallbacks : step.type === "utility" ? vocabulary?.utilities : step.type === "callback" ? vocabulary?.sharedCallbacks : void 0;
11059
- return table && Object.hasOwn(table, step.action) ? table[step.action] : void 0;
11060
- };
11061
- new Set(Object.keys({
11062
- "page-target-url": (element, _ctx, report) => {
11063
- const { href, mode } = element.attributes;
11064
- if (element.definition.type === "link" && (mode ?? "page") === "page" && isUrlTarget(href)) {
11065
- element.attributes.mode = "external";
11066
- report(`The link to "${href}" now opens it as an external URL.`, {
11067
- on: "attribute",
11068
- op: "set",
11069
- key: "mode",
11070
- value: "external"
11071
- });
11072
- }
11073
- for (const step of stepsOf$1(element)) if (step.action === "navigate" && step.params.urlType === "page" && isUrlTarget(step.params.url)) {
11074
- step.params.urlType = "external";
11075
- report(`Step "${step.id}" now navigates to "${step.params.url}" as an external URL.`, {
11076
- on: { step: step.id },
11077
- op: "set",
11078
- key: "urlType",
11079
- value: "external"
11080
- });
11081
- }
11082
- },
11083
- "unknown-attribute": (element, ctx, report) => {
11084
- const names = ctx.attributeNamesFor(element);
11085
- if (!isRoot(element) && names) settleUnknownKeys(element.attributes, names, "attribute", "attribute", report, ctx.defaultsFor(element));
11086
- },
11087
- "attribute-kind": (element, ctx, report) => {
11088
- const defaults = ctx.catalogs.defaultAttributes?.[element.definition.type] ?? {};
11089
- for (const [name, value] of Object.entries(element.attributes)) {
11090
- const flag = asFlag(value);
11091
- if (typeof defaults[name] === "boolean" && flag !== void 0) {
11092
- element.attributes[name] = flag;
11093
- report(`\`${name}\` is now the boolean ${String(flag)}, not the text "${String(value)}".`, {
11094
- on: "attribute",
11095
- op: "set",
11096
- key: name,
11097
- value: flag
11098
- });
11099
- }
11100
- }
11101
- },
11102
- "step-params": (element, ctx, report) => {
11103
- for (const step of stepsOf$1(element)) {
11104
- const spec = stepSpec(ctx.catalogs, step);
11105
- if (!spec?.params) continue;
11106
- const params = spec.params;
11107
- if (spec.strictParams) settleUnknownKeys(step.params, Object.keys(params), `param of step "${step.id}"`, { step: step.id }, report);
11108
- for (const [key, value] of Object.entries(step.params)) {
11109
- const flag = asFlag(value);
11110
- if (Object.hasOwn(params, key) && params[key].type === "boolean" && flag !== void 0) {
11111
- step.params[key] = flag;
11112
- report(`Step "${step.id}": \`${key}\` is now the boolean ${String(flag)}.`, {
11113
- on: { step: step.id },
11114
- op: "set",
11115
- key,
11116
- value: flag
11117
- });
11118
- }
11119
- }
11120
- }
11121
- },
11122
- "global-callback-module": (element, ctx, report) => {
11123
- for (const step of stepsOf$1(element)) {
11124
- const declared = step.type === "globalCallback" && ctx.catalogs.vocabulary ? ctx.catalogs.vocabulary.globalCallbacks[step.action] : void 0;
11125
- if (declared && step.elementId !== declared.source) {
11126
- step.elementId = declared.source;
11127
- report(`Step "${step.id}" now runs "${step.action}" on "${declared.source}", the module that registers it.`, {
11128
- on: { step: step.id },
11129
- op: "set",
11130
- key: "on",
11131
- value: declared.source
11132
- });
11133
- }
11134
- }
11135
- },
11136
- "utility-module": (element, ctx, report) => {
11137
- for (const step of stepsOf$1(element)) {
11138
- const known = ctx.catalogs.vocabulary ? Object.hasOwn(ctx.catalogs.vocabulary.utilities, step.action) : false;
11139
- if (step.type === "utility" && known && step.elementId !== null) {
11140
- step.elementId = null;
11141
- report(`Step "${step.id}" runs the utility "${step.action}" on no element: a utility takes none.`, {
11142
- on: { step: step.id },
11143
- op: "remove",
11144
- key: "on"
11145
- });
11146
- }
11147
- }
11148
- },
11149
- "visibility-as-attribute": (element, _ctx, report) => {
11150
- const bindings = element.definition.bindings;
11151
- const moved = bindings?.attributes?.filter((binding) => binding.to === "visibility") ?? [];
11152
- if (!bindings || moved.length === 0) return;
11153
- bindings.attributes = bindings.attributes?.filter((binding) => binding.to !== "visibility");
11154
- bindings.initialState = [...bindings.initialState ?? [], ...moved];
11155
- report("The visibility binding now sets the visibility, rather than an attribute nothing reads.");
11156
- },
11157
- "binding-target-unknown": (element, ctx, report) => {
11158
- const names = ctx.attributeNamesFor(element);
11159
- const bindings = element.definition.bindings;
11160
- if (!names || !bindings?.attributes) return;
11161
- const reads = (binding) => binding.to === "visibility" || binding.to === "className" || names.includes(binding.to);
11162
- for (const binding of bindings.attributes.filter((candidate) => !reads(candidate))) report(`Removed the binding onto "${binding.to}", which a "${element.definition.type}" never reads.`, {
11163
- on: "binding",
11164
- op: "remove",
11165
- key: binding.to
11166
- });
11167
- bindings.attributes = bindings.attributes.filter(reads);
11168
- },
11169
- "unknown-transformer": (element, ctx, report) => {
11170
- const catalog = ctx.catalogs.transformers;
11171
- if (!catalog) return;
11172
- for (const binding of Object.values(element.definition.bindings ?? {}).flat()) for (const transformer of binding.transformers ?? []) {
11173
- const meant = Object.hasOwn(catalog, transformer.action) ? void 0 : closest$1(transformer.action, Object.keys(catalog));
11174
- if (meant !== void 0) {
11175
- report(`The binding of "${binding.to}" now runs "${meant}" (was "${transformer.action}").`, {
11176
- on: "binding",
11177
- op: "replace",
11178
- key: transformer.action,
11179
- to: meant
11180
- });
11181
- transformer.action = meant;
11182
- }
11183
- }
11184
- },
11185
- "list-items-ignored": (element, _ctx, report) => {
11186
- const { attributes } = element;
11187
- const bound = Object.values(element.definition.bindings ?? {}).flat().some((binding) => binding.to === "items");
11188
- const written = Array.isArray(attributes.items) && attributes.items.length > 0;
11189
- if (element.definition.type === "list" && attributes.source !== "controlled" && (bound || written)) {
11190
- attributes.source = "controlled";
11191
- report("The list now reads its items (`source: controlled`), one row per item.", {
11192
- on: "attribute",
11193
- op: "set",
11194
- key: "source",
11195
- value: "controlled"
11196
- });
11197
- }
11198
- },
11199
- "list-row-not-li": (element, ctx, report) => {
11200
- const { parentId } = element.definition;
11201
- const list = parentId ? ctx.element(parentId) : void 0;
11202
- if (list?.definition.type === "list" && list.attributes.source === "controlled" && isPlainContainer(element)) {
11203
- element.attributes.subType = "li";
11204
- report("The row is now an `<li>` (`subType: li`), an item of the list it renders in.", {
11205
- on: "attribute",
11206
- op: "set",
11207
- key: "subType",
11208
- value: "li"
11209
- });
11210
- }
11211
- },
11212
- "overlay-starts-open": (element, _ctx, report) => {
11213
- const { type, bindings } = element.definition;
11214
- const visibilityBound = (bindings?.initialState ?? []).some((binding) => binding.to === "visibility");
11215
- if ((type === "modalContainer" || type === "dialogContainer") && element.definition.initialState?.visibility !== false && !visibilityBound) {
11216
- element.definition.initialState = {
11217
- ...element.definition.initialState,
11218
- visibility: false
11219
- };
11220
- report(`The ${type} now starts closed, to be opened from a flow.`, {
11221
- on: "field",
11222
- op: "set",
11223
- key: "visible",
11224
- value: false
11225
- });
11226
- }
11227
- },
11228
- "state-key-has-runtime-prefix": (element, _ctx, report) => {
11229
- for (const step of stepsOf$1(element)) {
11230
- if (step.type !== "globalCallback" || step.elementId !== "state") continue;
11231
- for (const param of STATE_PATH_PARAMS[step.action] ?? []) {
11232
- const value = step.params[param];
11233
- if (typeof value === "string" && /^(?:runtime\.)?state\./.test(value)) {
11234
- const next = value.replace(/^(?:runtime\.)?state\./, "");
11235
- step.params[param] = next;
11236
- report(`Step "${step.id}": \`${param}\` is now "${next}" (was "${value}").`, {
11237
- on: { step: step.id },
11238
- op: "set",
11239
- key: param,
11240
- value: next
11241
- });
11242
- }
11243
- }
11244
- }
11245
- }
11246
- }));
11247
- var e$4 = ".Toastify__toast-container.plitzi-sdk-toasts";
11248
- var t$4 = [
11249
- {
11250
- selector: e$4,
11251
- fields: {
11252
- background: ["--toastify-color-light", "--toastify-color-dark"],
11253
- text: ["--toastify-text-color-light", "--toastify-text-color-dark"],
11254
- success: ["--toastify-color-success"],
11255
- danger: ["--toastify-color-error"],
11256
- warning: ["--toastify-color-warning"],
11257
- info: ["--toastify-color-info"],
11258
- radius: ["--toastify-toast-bd-radius"],
11259
- font: ["--toastify-font-family"],
11260
- shadow: ["--toastify-toast-shadow"],
11261
- padding: ["--toastify-toast-padding"],
11262
- minHeight: ["--toastify-toast-min-height"]
11263
- }
11264
- },
11265
- {
11266
- selector: `:where(${e$4} .Toastify__toast)`,
11267
- fields: {
11268
- fontSize: ["font-size"],
11269
- fontWeight: ["font-weight"],
11270
- lineHeight: ["line-height"],
11271
- border: ["border"]
11272
- }
11273
- },
11274
- {
11275
- selector: `${e$4} .Toastify__toast-icon`,
11276
- fields: {
11277
- iconSize: ["width"],
11278
- iconGap: ["margin-inline-end"]
11279
- }
11280
- },
11281
- {
11282
- selector: `${e$4} .Toastify__close-button`,
11283
- fields: { closeColor: ["color"] }
11284
- },
11285
- {
11286
- selector: `${e$4} .Toastify__close-button:not(:hover, :focus)`,
11287
- fields: { closeOpacity: ["opacity"] }
11288
- },
11289
- {
11290
- selector: `${e$4} .Toastify__progress-bar--wrp`,
11291
- fields: { progressHeight: ["height"] }
11292
- }
11293
- ];
11294
- var n$4 = t$4.flatMap((e) => Object.keys(e.fields));
11295
- var r$2$1 = (e) => n$4.includes(e);
11296
- n$4.filter(r$2$1);
11297
- var c$2$1 = (e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11298
- t$4.map((e) => {
11299
- let t = new Map(Object.entries(e.fields).flatMap(([e, t]) => r$2$1(e) ? t.map((t) => [t, e]) : []));
11300
- return {
11301
- pattern: RegExp(`(?:^|\\n)${c$2$1(e.selector)}\\s*\\{([^}]*)\\}`, "g"),
11302
- fieldOf: (e) => t.get(e)
11303
- };
11304
- });
11305
- Object.fromEntries(e$29.map((e) => [e, t$26(e)]));
11306
- /** Every kebab-case CSS property Plitzi's style engine understands. Values written to a definition must use these
11307
- * exact keys — camelCase or unknown keys are rejected. */
11308
- var cssProperties = Array.from(new Set(Object.values(t$30))).sort();
11309
- var cssPropertySet = new Set(cssProperties);
11310
- var isCssProperty = (key) => cssPropertySet.has(key);
11311
- var toKebab = (key) => key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
11312
- /**
11313
- * A property as an author may write it, in the spelling the document keeps: kebab-case already, or camelCase the way a
11314
- * React style object writes it — `paddingTop` is `padding-top`, `WebkitLineClamp` is `-webkit-line-clamp`. A custom
11315
- * property (`--brand`) is the author's own spelling and is kept.
11316
- */
11317
- var cssPropertyName = (key) => {
11318
- if (isCustomProperty(key) || !/[A-Z]/.test(key)) return key;
11319
- return /^(Webkit|Moz|Ms|O)(?=[A-Z])/.exec(key) ? `-${toKebab(key)}` : toKebab(key);
11320
- };
11321
- /**
11322
- * The properties a bare number is a whole value of — a weight, an opacity, a count, a ratio. Anywhere else a number is
11323
- * a length, and a length without a unit is no value at all: `gap: 16` is `16px`.
11324
- */
11325
- var UNITLESS = /* @__PURE__ */ new Set([
11326
- "opacity",
11327
- "z-index",
11328
- "font-weight",
11329
- "line-height",
11330
- "flex",
11331
- "flex-grow",
11332
- "flex-shrink",
11333
- "order",
11334
- "column-count",
11335
- "aspect-ratio",
11336
- "orphans",
11337
- "widows",
11338
- "zoom",
11339
- "scale",
11340
- "tab-size",
11341
- "animation-iteration-count",
11342
- "grid-row-start",
11343
- "grid-row-end",
11344
- "grid-column-start",
11345
- "grid-column-end",
11346
- "fill-opacity",
11347
- "stroke-opacity",
11348
- "stroke-width",
11349
- "-webkit-line-clamp"
11350
- ]);
11351
- /** A number written as a value, as CSS reads it: itself where a bare number is a whole value, pixels otherwise. */
11352
- var cssNumberValue = (property, value) => UNITLESS.has(property) || isCustomProperty(property) || value === 0 ? value : `${String(value)}px`;
11353
- /** If a camelCase key maps to a known kebab-case property, return it — used to name the correct key in an error. */
11354
- var suggestCssProperty = (key) => {
11355
- const kebab = toKebab(key);
11356
- return cssPropertySet.has(kebab) ? kebab : void 0;
11357
- };
11358
- /**
11359
- * A CSS custom property — `--brand`, `--space-4`.
11360
- *
11361
- * Not in the vocabulary and deliberately allowed anyway: a custom property is a declaration the browser resolves,
11362
- * not one the style editor has to offer a control for, and a space that defines its palette in one place and reads
11363
- * it back with `var(--brand)` is doing the right thing.
11364
- */
11365
- var isCustomProperty = (key) => key.startsWith("--");
11366
- var walkTopLevel = (value, onChar) => {
11367
- let depth = 0;
11368
- let quote = "";
11369
- for (const ch of value) {
11370
- if (quote) {
11371
- onChar(ch, false);
11372
- if (ch === quote) quote = "";
11373
- continue;
11374
- }
11375
- if (/["']/.test(ch)) {
11376
- quote = ch;
11377
- onChar(ch, false);
11378
- continue;
11379
- }
11380
- if (ch === "(") depth += 1;
11381
- else if (ch === ")") depth = Math.max(0, depth - 1);
11382
- onChar(ch, depth === 0);
11383
- }
11384
- };
11385
- /** Split a shorthand value into whitespace-separated tokens, keeping parenthesized and quoted runs whole. */
11386
- var splitTokens = (value) => {
11387
- const tokens = [];
11388
- let current = "";
11389
- walkTopLevel(value.trim(), (ch, topLevel) => {
11390
- if (topLevel && /\s/.test(ch)) {
11391
- if (current) {
11392
- tokens.push(current);
11393
- current = "";
10998
+ /** Split a shorthand value into whitespace-separated tokens, keeping parenthesized and quoted runs whole. */
10999
+ var splitTokens = (value) => {
11000
+ const tokens = [];
11001
+ let current = "";
11002
+ walkTopLevel(value.trim(), (ch, topLevel) => {
11003
+ if (topLevel && /\s/.test(ch)) {
11004
+ if (current) {
11005
+ tokens.push(current);
11006
+ current = "";
11394
11007
  }
11395
11008
  } else current += ch;
11396
11009
  });
@@ -12087,7 +11700,7 @@ var byProperty = (rules) => {
12087
11700
  return responsive;
12088
11701
  };
12089
11702
  /** The states a selector can react to — the closed list the style editor offers, read from where it is declared. */
12090
- var STYLE_STATES = e$25;
11703
+ var STYLE_STATES = e$26;
12091
11704
  var STYLE_STATE_SET = new Set(STYLE_STATES);
12092
11705
  var RULE_SET_KEYS = /* @__PURE__ */ new Set([
12093
11706
  "css",
@@ -12151,11 +11764,11 @@ var assertPseudo = (pseudo, part) => {
12151
11764
  const ruleSets = partRuleSets(part);
12152
11765
  for (const rules of ruleSets) for (const [property, value] of Object.entries(rules)) {
12153
11766
  if (property === "content") {
12154
- if (!i$16.includes(pseudo)) throw new AuthoringError("style-pseudo-content", `\`content\` on \`::${pseudo}\` draws nothing: only ${i$16.map((name) => `\`::${name}\``).join(", ")} take one.`);
11767
+ if (!i$17.includes(pseudo)) throw new AuthoringError("style-pseudo-content", `\`content\` on \`::${pseudo}\` draws nothing: only ${i$17.map((name) => `\`::${name}\``).join(", ")} take one.`);
12155
11768
  if (!m$4(String(value))) throw new AuthoringError("style-pseudo-content", `\`content: ${String(value)}\` on \`::${pseudo}\` is not text CSS can read, so nothing is drawn. Text goes in quotes inside the string — \`content: '"${String(value).replace(/"/g, "\\\"")}"'\` — and an empty box is \`'""'\`; \`counter()\`, \`attr()\` and \`url()\` are written as they are.`);
12156
11769
  continue;
12157
11770
  }
12158
- if (!u$7(pseudo, property)) throw new AuthoringError("style-pseudo-property", `\`${property}\` on \`::${pseudo}\` is dropped by every browser — \`::${pseudo}\` honours ${(l$8[pseudo] ?? []).map((name) => `\`${name}\``).join(", ")}. Style the element itself for the rest.`);
11771
+ if (!u$8(pseudo, property)) throw new AuthoringError("style-pseudo-property", `\`${property}\` on \`::${pseudo}\` is dropped by every browser — \`::${pseudo}\` honours ${(l$8[pseudo] ?? []).map((name) => `\`${name}\``).join(", ")}. Style the element itself for the rest.`);
12159
11772
  }
12160
11773
  if (a$14.includes(pseudo) && !ruleSets.some((rules) => Object.hasOwn(rules, "content"))) throw new AuthoringError("style-pseudo-content", `\`::${pseudo}\` has no \`content\`, so the browser draws nothing for it. Give it one — \`content: '""'\` for an empty box — in its rules or in a state's.`);
12161
11774
  };
@@ -12163,7 +11776,7 @@ var toPseudos = (pseudos) => {
12163
11776
  const parts = /* @__PURE__ */ new Map();
12164
11777
  for (const [name, spec] of Object.entries(pseudos ?? {})) {
12165
11778
  const bare = name.replace(/^:+/, "");
12166
- if (!n$19(name)) throw new AuthoringError("style-pseudo-unknown", `Unknown pseudo-element "${name}"${n$19(bare) ? ` — write it without the colons, "${bare}"` : didYouMean(bare, e$26)}. A class dresses ${e$26.join(", ")}.`);
11779
+ if (!n$19(name)) throw new AuthoringError("style-pseudo-unknown", `Unknown pseudo-element "${name}"${n$19(bare) ? ` — write it without the colons, "${bare}"` : didYouMean(bare, e$27)}. A class dresses ${e$27.join(", ")}.`);
12167
11780
  const part = partOf(spec);
12168
11781
  const rules = {
12169
11782
  base: toResponsive(part.css),
@@ -13759,37 +13372,489 @@ var createTw = ({ colors = {} } = {}) => (classes) => {
13759
13372
  });
13760
13373
  }
13761
13374
  }
13762
- const own = buckets.get("|")?.bucket ?? {};
13763
- const ownRanges = byRange(own);
13764
- const rules = { css: responsive(rangesOf(own)) };
13765
- for (const [key, { target, bucket }] of buckets) {
13766
- if (key === "|") continue;
13767
- const css = responsive(rangesOf(bucket, ownRanges));
13768
- if (target.state) rules.states = {
13769
- ...rules.states,
13770
- [target.state]: css
13771
- };
13772
- else if (target.ancestor) {
13773
- const ancestors = { ...rules.ancestors };
13774
- const { name, state } = target.ancestor;
13775
- const ancestor = ancestors[name] ?? {};
13776
- ancestors[name] = state ? {
13777
- ...ancestor,
13778
- states: {
13779
- ...ancestor.states,
13780
- [state]: css
13781
- }
13782
- } : {
13783
- ...ancestor,
13784
- css
13785
- };
13786
- rules.ancestors = ancestors;
13375
+ const own = buckets.get("|")?.bucket ?? {};
13376
+ const ownRanges = byRange(own);
13377
+ const rules = { css: responsive(rangesOf(own)) };
13378
+ for (const [key, { target, bucket }] of buckets) {
13379
+ if (key === "|") continue;
13380
+ const css = responsive(rangesOf(bucket, ownRanges));
13381
+ if (target.state) rules.states = {
13382
+ ...rules.states,
13383
+ [target.state]: css
13384
+ };
13385
+ else if (target.ancestor) {
13386
+ const ancestors = { ...rules.ancestors };
13387
+ const { name, state } = target.ancestor;
13388
+ const ancestor = ancestors[name] ?? {};
13389
+ ancestors[name] = state ? {
13390
+ ...ancestor,
13391
+ states: {
13392
+ ...ancestor.states,
13393
+ [state]: css
13394
+ }
13395
+ } : {
13396
+ ...ancestor,
13397
+ css
13398
+ };
13399
+ rules.ancestors = ancestors;
13400
+ }
13401
+ }
13402
+ return rules;
13403
+ };
13404
+ createTw();
13405
+ new Set(Object.entries(g$4.content.defaultStyle.subTypes).filter(([, variant]) => "field" in variant.style).map(([subType]) => subType));
13406
+ /** A container with no tag of its own named: the row `list-row-not-li` makes an `<li>` without asking. */
13407
+ var isPlainContainer = (element) => element.definition.type === "container" && (element.attributes.subType === void 0 || element.attributes.subType === "div");
13408
+ new Set(STEP_TYPES);
13409
+ /** A step's params that name a path under `runtime.state`, by action. */
13410
+ var STATE_PATH_PARAMS = {
13411
+ setState: ["key"],
13412
+ toggleState: ["key"],
13413
+ appendState: ["key"],
13414
+ removeState: ["key"],
13415
+ toggleInState: ["key"],
13416
+ clearState: ["key"],
13417
+ moveState: ["from", "to"]
13418
+ };
13419
+ var t$6 = [
13420
+ "fade",
13421
+ "fade-up",
13422
+ "fade-down",
13423
+ "slide-left",
13424
+ "slide-right",
13425
+ "slide-up",
13426
+ "scale",
13427
+ "zoom-in",
13428
+ "zoom-out",
13429
+ "pop",
13430
+ "bounce-in",
13431
+ "tilt",
13432
+ "spin-in"
13433
+ ];
13434
+ var n$6 = [
13435
+ "load",
13436
+ "view",
13437
+ "scroll"
13438
+ ];
13439
+ var r$4 = [
13440
+ "float",
13441
+ "pulse",
13442
+ "spin",
13443
+ "sway",
13444
+ "bounce",
13445
+ "wobble",
13446
+ "orbit",
13447
+ "heartbeat",
13448
+ "drift"
13449
+ ];
13450
+ var o$6 = {
13451
+ fade: [{
13452
+ at: 0,
13453
+ opacity: 0
13454
+ }],
13455
+ "fade-up": [{
13456
+ at: 0,
13457
+ opacity: 0,
13458
+ translate: "0 24px"
13459
+ }],
13460
+ "fade-down": [{
13461
+ at: 0,
13462
+ opacity: 0,
13463
+ translate: "0 -24px"
13464
+ }],
13465
+ "slide-left": [{
13466
+ at: 0,
13467
+ opacity: 0,
13468
+ translate: "32px 0"
13469
+ }],
13470
+ "slide-right": [{
13471
+ at: 0,
13472
+ opacity: 0,
13473
+ translate: "-32px 0"
13474
+ }],
13475
+ "slide-up": [{
13476
+ at: 0,
13477
+ opacity: 0,
13478
+ translate: "0 72px"
13479
+ }],
13480
+ scale: [{
13481
+ at: 0,
13482
+ opacity: 0,
13483
+ scale: "0.94"
13484
+ }],
13485
+ "zoom-in": [{
13486
+ at: 0,
13487
+ opacity: 0,
13488
+ scale: "0.6"
13489
+ }],
13490
+ "zoom-out": [{
13491
+ at: 0,
13492
+ opacity: 0,
13493
+ scale: "1.25"
13494
+ }],
13495
+ pop: [
13496
+ {
13497
+ at: 0,
13498
+ opacity: 0,
13499
+ scale: "0.5"
13500
+ },
13501
+ {
13502
+ at: 55,
13503
+ opacity: 1,
13504
+ scale: "1.08"
13505
+ },
13506
+ {
13507
+ at: 78,
13508
+ scale: "0.97"
13509
+ }
13510
+ ],
13511
+ "bounce-in": [
13512
+ {
13513
+ at: 0,
13514
+ opacity: 0,
13515
+ translate: "0 -64px"
13516
+ },
13517
+ {
13518
+ at: 55,
13519
+ opacity: 1,
13520
+ translate: "0 10px"
13521
+ },
13522
+ {
13523
+ at: 75,
13524
+ translate: "0 -5px"
13525
+ },
13526
+ {
13527
+ at: 90,
13528
+ translate: "0 2px"
13529
+ }
13530
+ ],
13531
+ tilt: [{
13532
+ at: 0,
13533
+ opacity: 0,
13534
+ translate: "0 32px",
13535
+ rotate: "-6deg"
13536
+ }],
13537
+ "spin-in": [{
13538
+ at: 0,
13539
+ opacity: 0,
13540
+ scale: "0.4",
13541
+ rotate: "-180deg"
13542
+ }]
13543
+ };
13544
+ Object.fromEntries(t$6.map((e) => {
13545
+ let [{ at: t, ...n }] = o$6[e];
13546
+ return [e, n];
13547
+ }));
13548
+ var c$4 = (e, t) => {
13549
+ let n = t * 2 * Math.PI, r = Math.round(Math.sin(n) * e * 10) / 10, i = Math.round(-Math.cos(n) * e * 10) / 10;
13550
+ return `translate(${String(r)}px, ${String(i)}px)`;
13551
+ };
13552
+ Array.from({ length: 9 }, (e, t) => ({
13553
+ at: t * 100 / 8,
13554
+ transform: c$4(6, t / 8)
13555
+ }));
13556
+ [
13557
+ "[data-motion-loop] { animation-play-state: running, running !important; }",
13558
+ "[data-motion-on=\"view\"][data-motion-enter], [data-motion-on=\"view\"][data-motion-stagger] > * { animation-play-state: running, running !important; }",
13559
+ "[data-motion-on=\"scroll\"][data-motion-enter], [data-motion-on=\"scroll\"][data-motion-stagger] > * { animation-timeline: auto, auto !important; }"
13560
+ ].join("\n");
13561
+ Object.entries(AUTHORING_CODES).filter(([, entry]) => entry.kind === "suggested").map(([code]) => code);
13562
+ var URL_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
13563
+ /** A page target that is really a URL: the one reading of `mailto:`/`https:` a page-mode link can never mean. */
13564
+ var isUrlTarget = (target) => typeof target === "string" && !e$7(target) && URL_SCHEME.test(target);
13565
+ var isRoot = (element) => !element.definition.parentId;
13566
+ var stepsOf$1 = (element) => Object.values(element.definition.interactions ?? {});
13567
+ /**
13568
+ * A key the element or step does not take, renamed to the one it was a typo of — when that one is free — or dropped:
13569
+ * it was never read, so dropping it changes nothing on screen.
13570
+ */
13571
+ var settleUnknownKeys = (record, known, what, on, report, defaults = {}) => {
13572
+ for (const key of Object.keys(record)) {
13573
+ if (known.includes(key)) continue;
13574
+ const wrapped = record[key];
13575
+ if (e$33(wrapped) && Object.keys(wrapped).length > 0 && Object.keys(wrapped).every((inner) => known.includes(inner))) {
13576
+ const inners = Object.keys(wrapped);
13577
+ if (inners.every((inner) => record[inner] === void 0 || record[inner] === defaults[inner])) {
13578
+ Object.assign(record, wrapped);
13579
+ Reflect.deleteProperty(record, key);
13580
+ report(`Moved ${inners.join(", ")} out of the ${what} "${key}", up to where ${inners.length === 1 ? "it is" : "they are"} read.`, {
13581
+ on,
13582
+ op: "unwrap",
13583
+ key
13584
+ });
13585
+ }
13586
+ continue;
13587
+ }
13588
+ const meant = closest$1(key, known);
13589
+ if (meant !== void 0 && record[meant] === void 0) {
13590
+ record[meant] = record[key];
13591
+ report(`Renamed the ${what} "${key}" to "${meant}".`, {
13592
+ on,
13593
+ op: "rename",
13594
+ key,
13595
+ to: meant
13596
+ });
13597
+ } else report(`Removed the ${what} "${key}", which nothing reads.`, {
13598
+ on,
13599
+ op: "remove",
13600
+ key
13601
+ });
13602
+ Reflect.deleteProperty(record, key);
13603
+ }
13604
+ };
13605
+ /** A flag written as the words `'true'`/`'false'`, stored as the boolean it is read as. */
13606
+ var asFlag = (value) => value === "true" ? true : value === "false" ? false : void 0;
13607
+ var stepSpec = (catalogs, step) => {
13608
+ const vocabulary = catalogs.vocabulary;
13609
+ const table = step.type === "globalCallback" ? vocabulary?.globalCallbacks : step.type === "utility" ? vocabulary?.utilities : step.type === "callback" ? vocabulary?.sharedCallbacks : void 0;
13610
+ return table && Object.hasOwn(table, step.action) ? table[step.action] : void 0;
13611
+ };
13612
+ new Set(Object.keys({
13613
+ "page-target-url": (element, _ctx, report) => {
13614
+ const { href, mode } = element.attributes;
13615
+ if (element.definition.type === "link" && (mode ?? "page") === "page" && isUrlTarget(href)) {
13616
+ element.attributes.mode = "external";
13617
+ report(`The link to "${href}" now opens it as an external URL.`, {
13618
+ on: "attribute",
13619
+ op: "set",
13620
+ key: "mode",
13621
+ value: "external"
13622
+ });
13623
+ }
13624
+ for (const step of stepsOf$1(element)) if (step.action === "navigate" && step.params.urlType === "page" && isUrlTarget(step.params.url)) {
13625
+ step.params.urlType = "external";
13626
+ report(`Step "${step.id}" now navigates to "${step.params.url}" as an external URL.`, {
13627
+ on: { step: step.id },
13628
+ op: "set",
13629
+ key: "urlType",
13630
+ value: "external"
13631
+ });
13632
+ }
13633
+ },
13634
+ "unknown-attribute": (element, ctx, report) => {
13635
+ const names = ctx.attributeNamesFor(element);
13636
+ if (!isRoot(element) && names) settleUnknownKeys(element.attributes, names, "attribute", "attribute", report, ctx.defaultsFor(element));
13637
+ },
13638
+ "attribute-kind": (element, ctx, report) => {
13639
+ const defaults = ctx.catalogs.defaultAttributes?.[element.definition.type] ?? {};
13640
+ for (const [name, value] of Object.entries(element.attributes)) {
13641
+ const flag = asFlag(value);
13642
+ if (typeof defaults[name] === "boolean" && flag !== void 0) {
13643
+ element.attributes[name] = flag;
13644
+ report(`\`${name}\` is now the boolean ${String(flag)}, not the text "${String(value)}".`, {
13645
+ on: "attribute",
13646
+ op: "set",
13647
+ key: name,
13648
+ value: flag
13649
+ });
13650
+ }
13651
+ }
13652
+ },
13653
+ "step-params": (element, ctx, report) => {
13654
+ for (const step of stepsOf$1(element)) {
13655
+ const spec = stepSpec(ctx.catalogs, step);
13656
+ if (!spec?.params) continue;
13657
+ const params = spec.params;
13658
+ if (spec.strictParams) settleUnknownKeys(step.params, Object.keys(params), `param of step "${step.id}"`, { step: step.id }, report);
13659
+ for (const [key, value] of Object.entries(step.params)) {
13660
+ const flag = asFlag(value);
13661
+ if (Object.hasOwn(params, key) && params[key].type === "boolean" && flag !== void 0) {
13662
+ step.params[key] = flag;
13663
+ report(`Step "${step.id}": \`${key}\` is now the boolean ${String(flag)}.`, {
13664
+ on: { step: step.id },
13665
+ op: "set",
13666
+ key,
13667
+ value: flag
13668
+ });
13669
+ }
13670
+ }
13671
+ }
13672
+ },
13673
+ "global-callback-module": (element, ctx, report) => {
13674
+ for (const step of stepsOf$1(element)) {
13675
+ const declared = step.type === "globalCallback" && ctx.catalogs.vocabulary ? ctx.catalogs.vocabulary.globalCallbacks[step.action] : void 0;
13676
+ if (declared && step.elementId !== declared.source) {
13677
+ step.elementId = declared.source;
13678
+ report(`Step "${step.id}" now runs "${step.action}" on "${declared.source}", the module that registers it.`, {
13679
+ on: { step: step.id },
13680
+ op: "set",
13681
+ key: "on",
13682
+ value: declared.source
13683
+ });
13684
+ }
13685
+ }
13686
+ },
13687
+ "utility-module": (element, ctx, report) => {
13688
+ for (const step of stepsOf$1(element)) {
13689
+ const known = ctx.catalogs.vocabulary ? Object.hasOwn(ctx.catalogs.vocabulary.utilities, step.action) : false;
13690
+ if (step.type === "utility" && known && step.elementId !== null) {
13691
+ step.elementId = null;
13692
+ report(`Step "${step.id}" runs the utility "${step.action}" on no element: a utility takes none.`, {
13693
+ on: { step: step.id },
13694
+ op: "remove",
13695
+ key: "on"
13696
+ });
13697
+ }
13698
+ }
13699
+ },
13700
+ "visibility-as-attribute": (element, _ctx, report) => {
13701
+ const bindings = element.definition.bindings;
13702
+ const moved = bindings?.attributes?.filter((binding) => binding.to === "visibility") ?? [];
13703
+ if (!bindings || moved.length === 0) return;
13704
+ bindings.attributes = bindings.attributes?.filter((binding) => binding.to !== "visibility");
13705
+ bindings.initialState = [...bindings.initialState ?? [], ...moved];
13706
+ report("The visibility binding now sets the visibility, rather than an attribute nothing reads.");
13707
+ },
13708
+ "binding-target-unknown": (element, ctx, report) => {
13709
+ const names = ctx.attributeNamesFor(element);
13710
+ const bindings = element.definition.bindings;
13711
+ if (!names || !bindings?.attributes) return;
13712
+ const reads = (binding) => binding.to === "visibility" || binding.to === "className" || names.includes(binding.to);
13713
+ for (const binding of bindings.attributes.filter((candidate) => !reads(candidate))) report(`Removed the binding onto "${binding.to}", which a "${element.definition.type}" never reads.`, {
13714
+ on: "binding",
13715
+ op: "remove",
13716
+ key: binding.to
13717
+ });
13718
+ bindings.attributes = bindings.attributes.filter(reads);
13719
+ },
13720
+ "unknown-transformer": (element, ctx, report) => {
13721
+ const catalog = ctx.catalogs.transformers;
13722
+ if (!catalog) return;
13723
+ for (const binding of Object.values(element.definition.bindings ?? {}).flat()) for (const transformer of binding.transformers ?? []) {
13724
+ const meant = Object.hasOwn(catalog, transformer.action) ? void 0 : closest$1(transformer.action, Object.keys(catalog));
13725
+ if (meant !== void 0) {
13726
+ report(`The binding of "${binding.to}" now runs "${meant}" (was "${transformer.action}").`, {
13727
+ on: "binding",
13728
+ op: "replace",
13729
+ key: transformer.action,
13730
+ to: meant
13731
+ });
13732
+ transformer.action = meant;
13733
+ }
13734
+ }
13735
+ },
13736
+ "list-items-ignored": (element, _ctx, report) => {
13737
+ const { attributes } = element;
13738
+ const bound = Object.values(element.definition.bindings ?? {}).flat().some((binding) => binding.to === "items");
13739
+ const written = Array.isArray(attributes.items) && attributes.items.length > 0;
13740
+ if (element.definition.type === "list" && attributes.source !== "controlled" && (bound || written)) {
13741
+ attributes.source = "controlled";
13742
+ report("The list now reads its items (`source: controlled`), one row per item.", {
13743
+ on: "attribute",
13744
+ op: "set",
13745
+ key: "source",
13746
+ value: "controlled"
13747
+ });
13748
+ }
13749
+ },
13750
+ "list-row-not-li": (element, ctx, report) => {
13751
+ const { parentId } = element.definition;
13752
+ const list = parentId ? ctx.element(parentId) : void 0;
13753
+ if (list?.definition.type === "list" && list.attributes.source === "controlled" && isPlainContainer(element)) {
13754
+ element.attributes.subType = "li";
13755
+ report("The row is now an `<li>` (`subType: li`), an item of the list it renders in.", {
13756
+ on: "attribute",
13757
+ op: "set",
13758
+ key: "subType",
13759
+ value: "li"
13760
+ });
13761
+ }
13762
+ },
13763
+ "overlay-starts-open": (element, _ctx, report) => {
13764
+ const { type, bindings } = element.definition;
13765
+ const visibilityBound = (bindings?.initialState ?? []).some((binding) => binding.to === "visibility");
13766
+ if ((type === "modalContainer" || type === "dialogContainer") && element.definition.initialState?.visibility !== false && !visibilityBound) {
13767
+ element.definition.initialState = {
13768
+ ...element.definition.initialState,
13769
+ visibility: false
13770
+ };
13771
+ report(`The ${type} now starts closed, to be opened from a flow.`, {
13772
+ on: "field",
13773
+ op: "set",
13774
+ key: "visible",
13775
+ value: false
13776
+ });
13777
+ }
13778
+ },
13779
+ "state-key-has-runtime-prefix": (element, _ctx, report) => {
13780
+ for (const step of stepsOf$1(element)) {
13781
+ if (step.type !== "globalCallback" || step.elementId !== "state") continue;
13782
+ for (const param of STATE_PATH_PARAMS[step.action] ?? []) {
13783
+ const value = step.params[param];
13784
+ if (typeof value === "string" && /^(?:runtime\.)?state\./.test(value)) {
13785
+ const next = value.replace(/^(?:runtime\.)?state\./, "");
13786
+ step.params[param] = next;
13787
+ report(`Step "${step.id}": \`${param}\` is now "${next}" (was "${value}").`, {
13788
+ on: { step: step.id },
13789
+ op: "set",
13790
+ key: param,
13791
+ value: next
13792
+ });
13793
+ }
13794
+ }
13795
+ }
13796
+ }
13797
+ }));
13798
+ var e$4 = ".Toastify__toast-container.plitzi-sdk-toasts";
13799
+ var t$4 = [
13800
+ {
13801
+ selector: e$4,
13802
+ fields: {
13803
+ background: ["--toastify-color-light", "--toastify-color-dark"],
13804
+ text: ["--toastify-text-color-light", "--toastify-text-color-dark"],
13805
+ success: ["--toastify-color-success"],
13806
+ danger: ["--toastify-color-error"],
13807
+ warning: ["--toastify-color-warning"],
13808
+ info: ["--toastify-color-info"],
13809
+ radius: ["--toastify-toast-bd-radius"],
13810
+ font: ["--toastify-font-family"],
13811
+ shadow: ["--toastify-toast-shadow"],
13812
+ padding: ["--toastify-toast-padding"],
13813
+ minHeight: ["--toastify-toast-min-height"]
13814
+ }
13815
+ },
13816
+ {
13817
+ selector: `:where(${e$4} .Toastify__toast)`,
13818
+ fields: {
13819
+ fontSize: ["font-size"],
13820
+ fontWeight: ["font-weight"],
13821
+ lineHeight: ["line-height"],
13822
+ border: ["border"]
13823
+ }
13824
+ },
13825
+ {
13826
+ selector: `${e$4} .Toastify__toast-icon`,
13827
+ fields: {
13828
+ iconSize: ["width"],
13829
+ iconGap: ["margin-inline-end"]
13787
13830
  }
13831
+ },
13832
+ {
13833
+ selector: `${e$4} .Toastify__close-button`,
13834
+ fields: { closeColor: ["color"] }
13835
+ },
13836
+ {
13837
+ selector: `${e$4} .Toastify__close-button:not(:hover, :focus)`,
13838
+ fields: { closeOpacity: ["opacity"] }
13839
+ },
13840
+ {
13841
+ selector: `${e$4} .Toastify__progress-bar--wrp`,
13842
+ fields: { progressHeight: ["height"] }
13788
13843
  }
13789
- return rules;
13790
- };
13791
- createTw();
13792
- [...new Set(STYLE_STATES.filter((state) => state !== "hidden"))].filter((state) => u$6(state) !== `:${state}`);
13844
+ ];
13845
+ var n$4 = t$4.flatMap((e) => Object.keys(e.fields));
13846
+ var r$2$1 = (e) => n$4.includes(e);
13847
+ n$4.filter(r$2$1);
13848
+ var c$2$1 = (e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13849
+ t$4.map((e) => {
13850
+ let t = new Map(Object.entries(e.fields).flatMap(([e, t]) => r$2$1(e) ? t.map((t) => [t, e]) : []));
13851
+ return {
13852
+ pattern: RegExp(`(?:^|\\n)${c$2$1(e.selector)}\\s*\\{([^}]*)\\}`, "g"),
13853
+ fieldOf: (e) => t.get(e)
13854
+ };
13855
+ });
13856
+ Object.fromEntries(e$30.map((e) => [e, t$26(e)]));
13857
+ [...new Set(STYLE_STATES.filter((state) => state !== "hidden"))].filter((state) => u$7(state) !== `:${state}`);
13793
13858
  new Set("break case catch class const continue debugger default delete do else enum export extends false finally for function if import in instanceof new null return super switch this throw true try typeof var void while with yield let static implements interface package private protected public await element styles".split(" "));
13794
13859
  new Set(STYLE_STATES);
13795
13860
  new Set([
@@ -14972,7 +15037,7 @@ var V$1 = (e) => {
14972
15037
  for (let n of e.filter((e) => x$2.test(e))) t.set(M$1(n), [...t.get(M$1(n)) ?? [], n]);
14973
15038
  return [...t].flatMap(([e, t]) => t.slice(1).map((n) => y$1("plugin-type-taken", `${o$1$1}/${n}/`, `${o$1$1}/${t[0]}/ and ${o$1$1}/${n}/ are both the plugin ${e}: the server registers a folder by its name with a small first letter, and keeps one.`, `Put them in one folder, or rename one: git mv ${o$1$1}/${n} ${o$1$1}/<AnotherName>`)));
14974
15039
  };
14975
- var H$1 = (t) => e$32(t.assets) ? Object.values(t.assets).flatMap((t) => e$32(t) && typeof t.src == "string" && typeof t.type == "string" ? [{
15040
+ var H$1 = (t) => e$33(t.assets) ? Object.values(t.assets).flatMap((t) => e$33(t) && typeof t.src == "string" && typeof t.type == "string" ? [{
14976
15041
  src: t.src,
14977
15042
  type: t.type,
14978
15043
  isMain: t.isMain === !0
@@ -14986,7 +15051,7 @@ var U$1 = (t, n) => {
14986
15051
  } catch (e) {
14987
15052
  return { problem: y$1("vendor-manifest-invalid", i, `${i} is not JSON (${e instanceof Error ? e.message : String(e)}), so ${n} does not run.`, a) };
14988
15053
  }
14989
- if (!e$32(o)) return { problem: y$1("vendor-manifest-invalid", i, `${i} is not a manifest, so ${n} does not run.`, a) };
15054
+ if (!e$33(o)) return { problem: y$1("vendor-manifest-invalid", i, `${i} is not a manifest, so ${n} does not run.`, a) };
14990
15055
  let s = H$1(o), l = s.find((e) => e.type === "script" && e.isMain) ?? s.find((e) => e.type === "script");
14991
15056
  if (!l) return { problem: y$1("vendor-script-missing", i, `${i} names no script to run, so ${n} does not run.`, a) };
14992
15057
  if (!C$1(t, path.posix.join(r, l.src), "file")) return { problem: y$1("vendor-script-missing", i, `${i} names ${l.src} as the script to run, and ${r}/${l.src} is not there, so ${n} does not run.`, a) };
@@ -15110,7 +15175,7 @@ var ce$1 = (e, t) => {
15110
15175
  var Z$1 = (t) => {
15111
15176
  try {
15112
15177
  let n = JSON.parse(readFileSync(path.join(t, "package.json"), "utf-8"));
15113
- return e$32(n) && [n.dependencies, n.devDependencies].some((t) => e$32(t) && "@plitzi/sdk-server" in t);
15178
+ return e$33(n) && [n.dependencies, n.devDependencies].some((t) => e$33(t) && "@plitzi/sdk-server" in t);
15114
15179
  } catch {
15115
15180
  return !1;
15116
15181
  }
@@ -15233,7 +15298,7 @@ var publicData = (folder) => fileReader(folder, "/");
15233
15298
  * never serves (`dataDir`) — for `authorSpace`'s `serverData`: bindings are held to the file, and a provider reading
15234
15299
  * one from the browser is refused.
15235
15300
  */
15236
- var projectData = (folder) => fileReader(folder, e$8);
15301
+ var projectData = (folder) => fileReader(folder, e$9);
15237
15302
  /**
15238
15303
  * The declaration of every plugin under `folder` — `src/plugins/<Name>/declaration.ts`, its default export — for
15239
15304
  * `authorSpace`'s `plugins`: what each fires, answers and reads. Found by folder, as the server finds the plugins
@@ -15252,10 +15317,10 @@ var pluginDeclarations = async (folder) => {
15252
15317
  }));
15253
15318
  };
15254
15319
  /** The manifest of a plugin run as it was built, read; nothing when it cannot be — the layout check says why first. */
15255
- var manifestOf$1 = (file) => {
15320
+ var manifestOf$2 = (file) => {
15256
15321
  try {
15257
15322
  const manifest = JSON.parse(readFileSync(file, "utf-8"));
15258
- return e$32(manifest) ? manifest : {};
15323
+ return e$33(manifest) ? manifest : {};
15259
15324
  } catch {
15260
15325
  return {};
15261
15326
  }
@@ -15265,8 +15330,8 @@ var manifestOf$1 = (file) => {
15265
15330
  * space): each folder's type and every element its manifest provides (`pluginSchema`). None in most projects.
15266
15331
  */
15267
15332
  var builtPluginTypes = (folder) => existsSync(folder) ? readdirSync(folder, { withFileTypes: true }).filter((entry) => entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name)).flatMap((entry) => {
15268
- const { pluginSchema } = manifestOf$1(path.join(folder, entry.name, d$1));
15269
- return [entry.name, ...e$32(pluginSchema) ? Object.keys(pluginSchema) : []];
15333
+ const { pluginSchema } = manifestOf$2(path.join(folder, entry.name, d$1));
15334
+ return [entry.name, ...e$33(pluginSchema) ? Object.keys(pluginSchema) : []];
15270
15335
  }) : [];
15271
15336
  /**
15272
15337
  * The stylesheets of the plugins under `folder` — every `.css` in a plugin's folder, at any depth — as their text, for
@@ -15335,7 +15400,7 @@ var importProjectModule = async (file, root) => {
15335
15400
  * What the module exports as `space`, taken as a declaration when it has the shape of one. `authorSpace` checks the
15336
15401
  * rest of it, field by field, and says what is wrong — so this only has to tell a space from anything else.
15337
15402
  */
15338
- var isSpaceSpec = (value) => e$32(value) && typeof value.name === "string" && typeof value.permanentUrl === "string" && Array.isArray(value.pages);
15403
+ var isSpaceSpec = (value) => e$33(value) && typeof value.name === "string" && typeof value.permanentUrl === "string" && Array.isArray(value.pages);
15339
15404
  /**
15340
15405
  * `projectSpace` of the project at `root`, held to the same checks: for a tool that works on a project from another
15341
15406
  * folder — the CLI, run anywhere inside one. A project's own code calls `projectSpace()` or `authorProjectSpace()`.
@@ -15343,7 +15408,7 @@ var isSpaceSpec = (value) => e$32(value) && typeof value.name === "string" && ty
15343
15408
  var projectSpaceAt = async (root) => {
15344
15409
  const authoring = await projectAuthoringAt(root);
15345
15410
  const module = await importProjectModule(l$2(root, a$1, process.argv[1]), root);
15346
- const space = e$32(module) ? module.space : void 0;
15411
+ const space = e$33(module) ? module.space : void 0;
15347
15412
  if (!isSpaceSpec(space)) throw new ProjectSpaceError(a$1);
15348
15413
  return {
15349
15414
  space,
@@ -16294,7 +16359,8 @@ var unifiedDiff = (file, before, after, context = 2) => {
16294
16359
  * is left to the author, said with the command that would take the registry's.
16295
16360
  * - `skills`: `.claude/skills/plitzi-*` from the packages installed, each replaced whole.
16296
16361
  * - `renames`: a name a version renamed, still written in the source (`canTemplate`), at its file and line — and with
16297
- * `--write`, renamed.
16362
+ * `--write`, renamed. With it, what a version changed that the source may count on — a `formControl` written
16363
+ * before fields became optional — at its file and line, for a project last upgraded before: said, never written.
16298
16364
  *
16299
16365
  * Shown by default; `--write` makes it. What the space is — its pages, plugins, data — is the project's, never named.
16300
16366
  *
@@ -16355,6 +16421,42 @@ var renamesIn = (file, text) => RENAMES.filter((rename) => !rename.from || impor
16355
16421
  since: rename.since,
16356
16422
  what: rename.what
16357
16423
  }] : []));
16424
+ /** Before this version a `formControl` was required unless it said `required: false`; from it, optional unless `true`. */
16425
+ var OPTIONAL_FIELDS_SINCE = "0.38.9";
16426
+ /** The text of the call whose `(` is at `open`, up to its `)` — strings skipped, so a `)` inside one closes nothing. */
16427
+ var callArguments = (text, open) => {
16428
+ let depth = 0;
16429
+ let quote;
16430
+ for (let at = open; at < text.length; at++) {
16431
+ const char = text[at];
16432
+ if (quote && char === "\\") {
16433
+ at++;
16434
+ continue;
16435
+ }
16436
+ if (quote) {
16437
+ quote = char === quote ? void 0 : quote;
16438
+ continue;
16439
+ }
16440
+ if (/['"`]/.test(char)) quote = char;
16441
+ else if (char === "(") depth++;
16442
+ else if (char === ")" && --depth === 0) return text.slice(open + 1, at);
16443
+ }
16444
+ return text.slice(open + 1);
16445
+ };
16446
+ /**
16447
+ * A `formControl(…)` that writes no `required`: one that, written before fields became optional, was required, and now
16448
+ * takes an empty answer. Said, never written — whether a field must be answered is the space's to say. Found as written
16449
+ * in the call: one built by a helper of the project's own is the helper's to look at.
16450
+ */
16451
+ var optionalFieldsIn = (file, text) => [...text.matchAll(/\bformControl\(/g)].flatMap((match) => {
16452
+ const open = match.index + match[0].length - 1;
16453
+ return /\brequired\s*:/.test(callArguments(text, open)) ? [] : [{
16454
+ file,
16455
+ line: text.slice(0, match.index).split("\n").length,
16456
+ since: OPTIONAL_FIELDS_SINCE,
16457
+ what: "a formControl with no `required` is optional now — write `required: true` if it must be answered"
16458
+ }];
16459
+ });
16358
16460
  /** The lowest version a range allows, as numbers: `^0.38.2` → [0, 38, 2]. */
16359
16461
  var floorOf$1 = (range) => {
16360
16462
  const match = /(\d+)\.(\d+)\.(\d+)/.exec(range);
@@ -16451,8 +16553,20 @@ var readText$1 = async (file) => {
16451
16553
  return;
16452
16554
  }
16453
16555
  };
16556
+ var holdsFiles = async (folder) => {
16557
+ try {
16558
+ return (await fs.readdir(folder)).length > 0;
16559
+ } catch {
16560
+ return false;
16561
+ }
16562
+ };
16454
16563
  var planFiles = async (root, files, recorded, take) => Promise.all(Object.entries(files).map(async ([file, ours]) => {
16455
16564
  const yours = await readText$1(path.join(root, file));
16565
+ if (yours === void 0 && path.basename(file) === ".gitkeep" && await holdsFiles(path.join(root, file, ".."))) return {
16566
+ file,
16567
+ status: "current",
16568
+ ours
16569
+ };
16456
16570
  if (yours === void 0) return {
16457
16571
  file,
16458
16572
  status: "added",
@@ -16611,6 +16725,10 @@ var reportText$3 = (report, install) => {
16611
16725
  lines.push(report.renames.length === 0 ? chalk.green("renames: none written") : "renames:");
16612
16726
  lines.push(...report.renames.map((found) => chalk.yellow(` ${report.write ? "~" : "!"} ${found.file}:${String(found.line)} ${found.name} → ${found.to} (${found.since}: ${found.what})`)));
16613
16727
  }
16728
+ if (report.changes && report.changes.length > 0) {
16729
+ lines.push("changes the source may count on (look, then decide — never written):");
16730
+ lines.push(...report.changes.map((found) => chalk.yellow(` ! ${found.file}:${String(found.line)} (${found.since}) ${found.what}`)));
16731
+ }
16614
16732
  return lines.join("\n");
16615
16733
  };
16616
16734
  var upgrade = async (parts, options) => {
@@ -16707,8 +16825,11 @@ var upgrade = async (parts, options) => {
16707
16825
  }
16708
16826
  if (wanted.has("renames")) {
16709
16827
  const found = [];
16828
+ const changes = [];
16829
+ const before = record === void 0 || isBelow(record.cli, OPTIONAL_FIELDS_SINCE);
16710
16830
  for (const file of await sourceFiles(root)) {
16711
16831
  const text = await readText$1(path.join(root, file));
16832
+ if (before && text !== void 0) changes.push(...optionalFieldsIn(file, text));
16712
16833
  const inFile = text === void 0 ? [] : renamesIn(file, text);
16713
16834
  if (write && text !== void 0 && inFile.length > 0) {
16714
16835
  const renamed = [...new Set(inFile.map((entry) => entry.name))].reduce((result, name) => result.replace(wordOf(name), RENAMES.find((rename) => rename.name === name)?.to ?? name), text);
@@ -16717,6 +16838,7 @@ var upgrade = async (parts, options) => {
16717
16838
  found.push(...inFile);
16718
16839
  }
16719
16840
  report.renames = found;
16841
+ report.changes = changes;
16720
16842
  }
16721
16843
  console.log(options.json ? JSON.stringify(report) : reportText$3(report, installCommand(manager)));
16722
16844
  if (report.packages?.install === "failed") {
@@ -18389,15 +18511,32 @@ var checkAccount = async (root, username) => {
18389
18511
  password
18390
18512
  };
18391
18513
  };
18514
+ /** Signs the page's browser in as the account, through the server's `/auth` routes: what went wrong, or nothing. */
18515
+ var signInAs = async (page, origin, account) => {
18516
+ const answer = await page.request.post(`${origin}${SIGN_IN_PATH}`, { data: {
18517
+ username: account.username,
18518
+ password: account.password
18519
+ } });
18520
+ if (answer.ok()) return;
18521
+ return answer.status() === 404 ? `nothing answers at ${SIGN_IN_PATH}: --as signs in through the routes createServer({ auth }) mounts there` : `${account.username} was not signed in (${String(answer.status())}): ${(await answer.text()).slice(0, 200)} — check ${CHECK_PASSWORD_ENV} in .env`;
18522
+ };
18523
+ var withoutTrailingSlash = (path) => path.length > 1 ? path.replace(/\/+$/, "") : path;
18524
+ /** Where the page landed, when that is not the path it was opened at — sent to the sign-in, most often. */
18525
+ var sentElsewhere = (page, pathname) => {
18526
+ const landed = new URL(page.url()).pathname;
18527
+ const asked = new URL(pathname, page.url()).pathname;
18528
+ return withoutTrailingSlash(landed) === withoutTrailingSlash(asked) ? void 0 : landed;
18529
+ };
18392
18530
  /** The space the project declares, authored. Only for one written here. */
18393
18531
  var projectSpace = async (root) => {
18394
18532
  const project = await loadProjectSpace(root);
18395
18533
  if ("problem" in project) return { problem: `${project.problem} There is nothing to check the page against.` };
18396
18534
  try {
18397
- const { handles, schema } = authorSpace(project.space, project.authoring);
18535
+ const { handles, schema, style } = authorSpace(project.space, project.authoring);
18398
18536
  return {
18399
18537
  handles,
18400
- schema
18538
+ schema,
18539
+ schemes: SCHEMES.filter((scheme) => style.theme.schemes.includes(scheme))
18401
18540
  };
18402
18541
  } catch (error) {
18403
18542
  return { problem: `The space does not author — npm run author says why: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}` };
@@ -18628,7 +18767,7 @@ var momentChanges = (before, after, state) => {
18628
18767
  ...stateChanges
18629
18768
  ];
18630
18769
  };
18631
- var selectorOf$1 = (authored, id) => authored && Object.hasOwn(authored.handles.elements, id) ? authored.handles.elements[id].selector : `[data-plitzi-el="${id}"]`;
18770
+ var selectorOf = (authored, id) => authored && Object.hasOwn(authored.handles.elements, id) ? authored.handles.elements[id].selector : `[data-plitzi-el="${id}"]`;
18632
18771
  var FIELDS = "input, textarea, select";
18633
18772
  /** The elements of the space the fields inside a node belong to: the nearest element around each. */
18634
18773
  var fieldOwnersInPage = (node, selector) => Array.from(node.querySelectorAll(selector), (field) => field.closest("[data-plitzi-el]")?.getAttribute("data-plitzi-el")).map((owner) => owner ?? "?");
@@ -18638,7 +18777,7 @@ var fieldOwnersInPage = (node, selector) => Array.from(node.querySelectorAll(sel
18638
18777
  * it is another's; or what could not be filled, and why.
18639
18778
  */
18640
18779
  var fillField = async (page, authored, { element, value }) => {
18641
- const node = page.locator(selectorOf$1(authored, element)).first();
18780
+ const node = page.locator(selectorOf(authored, element)).first();
18642
18781
  if (await node.count() === 0) return { problem: `no element ${element} is on this page to fill` };
18643
18782
  const own = await node.evaluate((each, selector) => each.matches(selector), FIELDS);
18644
18783
  const owners = own ? [element] : await node.evaluate(fieldOwnersInPage, FIELDS);
@@ -18663,7 +18802,7 @@ var fillField = async (page, authored, { element, value }) => {
18663
18802
  * nothing is said as such: a button whose flow does nothing looks the same as one that works until somebody clicks.
18664
18803
  */
18665
18804
  var clickEffects = async (page, authored, { element: id, fill }) => {
18666
- const target = page.locator(selectorOf$1(authored, id)).first();
18805
+ const target = page.locator(selectorOf(authored, id)).first();
18667
18806
  if (await target.count() === 0) return {
18668
18807
  element: id,
18669
18808
  problem: `no element ${id} is on this page`,
@@ -18792,12 +18931,12 @@ var lateElements = (schema, served, hydrated, width) => {
18792
18931
  browserOnly
18793
18932
  };
18794
18933
  };
18795
- var checkAt = async (browser, origin, pathname, width, scheme, authored, asked, ssr, account, click) => {
18934
+ var checkAt = async (browser, origin, pathname, width, view, authored, asked, ssr, account, click) => {
18796
18935
  const handles = authored?.handles;
18797
18936
  const page = await openProjectPage(browser, origin, {
18798
18937
  width,
18799
18938
  height: 900,
18800
- ...scheme ? { scheme } : {}
18939
+ ...view
18801
18940
  });
18802
18941
  /** A report of a page that could not be looked at — nothing answered, no sign-in, sent elsewhere. */
18803
18942
  const unchecked = (issue) => ({
@@ -18813,17 +18952,12 @@ var checkAt = async (browser, origin, pathname, width, scheme, authored, asked,
18813
18952
  devTools: false,
18814
18953
  lists: {}
18815
18954
  });
18816
- if (account) {
18817
- const signedIn = await page.request.post(`${origin}${SIGN_IN_PATH}`, { data: {
18818
- username: account.username,
18819
- password: account.password
18820
- } });
18821
- if (!signedIn.ok()) return unchecked({
18822
- code: "not-signed-in",
18823
- message: signedIn.status() === 404 ? `nothing answers at ${SIGN_IN_PATH}: --as signs in through the routes createServer({ auth }) mounts there` : `${account.username} was not signed in (${String(signedIn.status())}): ${(await signedIn.text()).slice(0, 200)} — check ${CHECK_PASSWORD_ENV} in .env`,
18824
- width
18825
- });
18826
- }
18955
+ const notSignedIn = account && await signInAs(page, origin, account);
18956
+ if (notSignedIn) return unchecked({
18957
+ code: "not-signed-in",
18958
+ message: notSignedIn,
18959
+ width
18960
+ });
18827
18961
  const consoleErrors = [];
18828
18962
  const failedRequests = [];
18829
18963
  page.on("pageerror", (error) => consoleErrors.push(error.message));
@@ -18857,8 +18991,8 @@ var checkAt = async (browser, origin, pathname, width, scheme, authored, asked,
18857
18991
  consoleErrors,
18858
18992
  failedRequests
18859
18993
  };
18860
- const landed = new URL(page.url()).pathname;
18861
- if (withoutTrailingSlash(landed) !== withoutTrailingSlash(new URL(`${origin}${pathname}`).pathname)) return {
18994
+ const landed = sentElsewhere(page, pathname);
18995
+ if (landed) return {
18862
18996
  ...unchecked({
18863
18997
  code: "redirected",
18864
18998
  message: account ? `${pathname} sent the browser to ${landed}, signed in as ${account.username}: that account may not see it` : `${pathname} sent the browser to ${landed} — what a page for signed-in visitors does: \`plitzi page check ${pathname} --as <username>\` signs in first, the password in ${CHECK_PASSWORD_ENV} in .env`,
@@ -18986,7 +19120,7 @@ var clickedText = (clicked) => {
18986
19120
  ];
18987
19121
  };
18988
19122
  var reportText$2 = (report, asked) => {
18989
- const head = `${report.path} at ${String(report.width)} px`;
19123
+ const head = `${report.path} at ${String(report.width)} px${report.scheme ? `, ${report.scheme}` : ""}`;
18990
19124
  const held = [
18991
19125
  ...report.notFound ? [" · sent with status 404: the page for an address that shows nothing"] : [],
18992
19126
  ...listsText(report.lists),
@@ -19008,7 +19142,6 @@ var reportText$2 = (report, asked) => {
19008
19142
  ...held
19009
19143
  ].join("\n");
19010
19144
  };
19011
- var withoutTrailingSlash = (path) => path.length > 1 ? path.replace(/\/+$/, "") : path;
19012
19145
  var check = async (routes, options) => {
19013
19146
  const project = await projectHere("whose page to check");
19014
19147
  if (!project) return;
@@ -19034,6 +19167,7 @@ var check = async (routes, options) => {
19034
19167
  asked,
19035
19168
  ssr: Boolean(options.ssr),
19036
19169
  ...options.scheme ? { scheme: options.scheme } : {},
19170
+ presence: options.presence === true,
19037
19171
  ...account ? { account } : {},
19038
19172
  ...options.click ? { click: {
19039
19173
  element: options.click,
@@ -19062,17 +19196,29 @@ var checkRoutes = async (project, routes, options) => {
19062
19196
  if ("problem" in browser) return browser;
19063
19197
  try {
19064
19198
  const reports = [];
19065
- for (const route of routes) for (const width of options.widths) reports.push(await checkAt(browser, where.origin, route, width, options.scheme, authored, options.asked, options.ssr, options.account, options.click));
19199
+ for (const route of routes) for (const width of options.widths) {
19200
+ const report = await checkAt(browser, where.origin, route, width, {
19201
+ ...options.scheme ? { scheme: options.scheme } : {},
19202
+ presence: options.presence === true
19203
+ }, authored, options.asked, options.ssr, options.account, options.click);
19204
+ reports.push({
19205
+ ...report,
19206
+ ...options.scheme ? { scheme: options.scheme } : {}
19207
+ });
19208
+ }
19066
19209
  return reports;
19067
19210
  } finally {
19068
19211
  await browser.close();
19069
19212
  }
19070
19213
  };
19071
- /** The paths of the space's pages that take no parameter — the ones a check can open as they are. */
19214
+ /** What a whole-project check opens: every page with no parameter in its path, in every theme the space can show. */
19072
19215
  var staticPaths = async (root) => {
19073
19216
  const authored = await projectSpace(root);
19074
19217
  if ("problem" in authored) return authored;
19075
- return Object.values(authored.handles.pages).map((page) => isNotFoundPage(page) ? `${page.path.slice(0, -1)}${NOT_FOUND_PROBE}` : page.path).filter((pathname) => pathname.split("/").every((segment) => !segment.startsWith(":") && !/\{\{.*\}\}/.test(segment)));
19218
+ return {
19219
+ paths: Object.values(authored.handles.pages).map((page) => isNotFoundPage(page) ? `${page.path.slice(0, -1)}${NOT_FOUND_PROBE}` : page.path).filter((pathname) => pathname.split("/").every((segment) => !segment.startsWith(":") && !/\{\{.*\}\}/.test(segment))),
19220
+ schemes: authored.schemes
19221
+ };
19076
19222
  };
19077
19223
  //#endregion
19078
19224
  //#region src/commands/functions.ts
@@ -19297,16 +19443,6 @@ var projectRunner = async (root) => {
19297
19443
  } catch {}
19298
19444
  fail("plitzi functions dev runs them with this project’s own @plitzi/sdk-server, and the isolates it needs:\n npm install --save-dev @plitzi/sdk-server isolated-vm core-js");
19299
19445
  };
19300
- /** `PLITZI_FUNCTIONS_CREDENTIALS`, a JSON object of credential id → its keys: what `ctx.fetch` may name locally. */
19301
- var localCredentials = () => {
19302
- const raw = process.env.PLITZI_FUNCTIONS_CREDENTIALS;
19303
- try {
19304
- const value = raw ? JSON.parse(raw) : {};
19305
- return e$3(value) ? Object.fromEntries(Object.entries(value).flatMap(([id, keys]) => e$3(keys) ? [[id, Object.fromEntries(Object.entries(keys).filter((pair) => typeof pair[1] === "string"))]] : [])) : {};
19306
- } catch {
19307
- return {};
19308
- }
19309
- };
19310
19446
  var NO_NODE_SNAPSHOT = "--no-node-snapshot";
19311
19447
  /**
19312
19448
  * The isolates `dev` runs need Node's startup snapshot off, and a process cannot turn it off once started: `dev` runs
@@ -19336,10 +19472,7 @@ var devFunction = async (task, options) => {
19336
19472
  const root = params && await rootOf();
19337
19473
  const runner = root ? await projectRunner(root) : void 0;
19338
19474
  if (!root || !runner) return;
19339
- const local = runner.createLocalFunctions({
19340
- credentials: localCredentials(),
19341
- dataDir: path.join(root, m)
19342
- });
19475
+ const local = runner.createLocalFunctions({ dataDir: path.join(root, m) });
19343
19476
  const run = async () => {
19344
19477
  const loaded = await local.load(await readFunctionsFiles(root));
19345
19478
  if (!loaded.ok) {
@@ -21104,6 +21237,125 @@ var fix = async (options) => {
21104
21237
  ].join("\n"));
21105
21238
  };
21106
21239
  //#endregion
21240
+ //#region src/commands/functionsAdd.ts
21241
+ /** Where a package added to the functions is kept: their own code from then on, copied in. */
21242
+ var VENDOR_DIR = "vendor";
21243
+ /** What a space's functions may weigh built, whole — a package added is part of it (`@plitzi/sdk-server`'s cap). */
21244
+ var MAX_FUNCTIONS_BYTES = 1e6;
21245
+ /** A package's name, as npm writes one: `ical.js`, `@scope/name`. */
21246
+ var PACKAGE_NAME = /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i;
21247
+ /** Globals of Node a package may read and a runner does not have: said, since a bundle cannot tell if they run. */
21248
+ var NODE_GLOBALS = /\b(process\.(env|nextTick|platform|versions?)|Buffer\.(from|alloc|isBuffer)|__dirname|__filename)\b/;
21249
+ /** The file a package is kept in, under `src/functions/vendor/`: its name, with its scope folded in. */
21250
+ var vendorFileOf = (name) => `${name.replace(/^@/, "").replace("/", "__")}.js`;
21251
+ /** The package's own `package.json`, found from where it resolves: its version and its licence for the header. */
21252
+ var manifestOf$1 = async (entry, name) => {
21253
+ for (let dir = path.dirname(entry); dir !== path.dirname(dir); dir = path.dirname(dir)) try {
21254
+ const parsed = JSON.parse(await fs.readFile(path.join(dir, "package.json"), "utf8"));
21255
+ if (e$3(parsed) && parsed.name === name) return {
21256
+ version: typeof parsed.version === "string" ? parsed.version : "?",
21257
+ license: typeof parsed.license === "string" ? parsed.license : "no licence stated"
21258
+ };
21259
+ } catch {}
21260
+ return {
21261
+ version: "?",
21262
+ license: "no licence stated"
21263
+ };
21264
+ };
21265
+ /**
21266
+ * One package bundled for the functions: web APIs only, as they run — what reaches Node (`fs`, `node:crypto`) does
21267
+ * not resolve on a neutral platform, and is the reason it is refused. With what it exports, for its types.
21268
+ */
21269
+ var bundle = async (root, name, withDefault) => {
21270
+ try {
21271
+ const result = await build({
21272
+ stdin: {
21273
+ contents: `export * from '${name}';${withDefault ? `\nexport { default } from '${name}';` : ""}`,
21274
+ resolveDir: root,
21275
+ loader: "js"
21276
+ },
21277
+ bundle: true,
21278
+ write: false,
21279
+ metafile: true,
21280
+ format: "esm",
21281
+ platform: "neutral",
21282
+ mainFields: ["module", "main"],
21283
+ target: "es2022",
21284
+ minify: true,
21285
+ legalComments: "inline",
21286
+ logLevel: "silent"
21287
+ });
21288
+ const exports = Object.values(result.metafile.outputs).flatMap((output) => output.exports);
21289
+ return {
21290
+ code: result.outputFiles.map((output) => output.text).join(""),
21291
+ exports
21292
+ };
21293
+ } catch (error) {
21294
+ return { errors: e$3(error) && Array.isArray(error.errors) ? error.errors.flatMap((entry) => e$3(entry) && typeof entry.text === "string" ? [{ text: entry.text }] : []) : [{ text: error instanceof Error ? error.message : String(error) }] };
21295
+ }
21296
+ };
21297
+ /** Why esbuild could not bundle it, in the words that say what to do: a Node module is a package that cannot run here. */
21298
+ var refusalOf$1 = (name, errors) => {
21299
+ const node = errors.map((entry) => /Could not resolve "([^"]+)"/.exec(entry.text)?.[1]).find((module) => module !== void 0 && !module.startsWith("."));
21300
+ return node ? `${name} reaches "${node}", which the functions do not have: they run with web APIs only — fetch, crypto.subtle, URL — on any runner, so a package that needs Node cannot run there. Look for one written for the browser or the edge.` : `${name} could not be bundled: ${errors.map((entry) => entry.text).join("; ")}`;
21301
+ };
21302
+ /**
21303
+ * `plitzi functions add <package>`: a package the functions use, as a function carries its dependencies — bundled into
21304
+ * `src/functions/vendor/`, and from then on their own code: pushed and built with the rest, read by the builder, run by
21305
+ * the runner. Nothing is resolved anywhere else, so a space's functions stay its own files. Installed first, with the
21306
+ * project's package manager, so its version is the project's choice and its types reach the editor.
21307
+ */
21308
+ var addFunctionsPackage = async (name, options) => {
21309
+ if (!PACKAGE_NAME.test(name)) {
21310
+ fail(`"${name}" is not a package's name — ical.js, @scope/name.`);
21311
+ return;
21312
+ }
21313
+ const root = (await projectHere("whose functions these are"))?.root;
21314
+ if (!root) return;
21315
+ let entry;
21316
+ try {
21317
+ entry = createRequire(path.join(root, "package.json")).resolve(name);
21318
+ } catch {
21319
+ try {
21320
+ entry = createRequire(path.join(root, "package.json")).resolve(`${name}/package.json`);
21321
+ } catch {
21322
+ fail(`${name} is not installed in this project: install it first (npm install --save-dev ${name}), then add it.`);
21323
+ return;
21324
+ }
21325
+ }
21326
+ let built = await bundle(root, name, true);
21327
+ if ("errors" in built && built.errors.some((entry) => /No matching export .*"default"/.test(entry.text))) built = await bundle(root, name, false);
21328
+ if ("errors" in built) {
21329
+ fail(refusalOf$1(name, built.errors));
21330
+ return;
21331
+ }
21332
+ const bytes = Buffer.byteLength(built.code);
21333
+ if (bytes > MAX_FUNCTIONS_BYTES) {
21334
+ fail(`${name} is ${String(Math.round(bytes / 1024))} KB bundled, and a space's functions are at most ${String(MAX_FUNCTIONS_BYTES / 1e3)} KB built, all of it: look for a smaller one, or import less of it.`);
21335
+ return;
21336
+ }
21337
+ const { version, license } = await manifestOf$1(entry, name);
21338
+ const file = vendorFileOf(name);
21339
+ const relative = `./${VENDOR_DIR}/${file}`;
21340
+ const header = `// ${name}@${version} — ${license}. Added by \`plitzi functions add ${name}\`: the package bundled for the functions\n// (web APIs only), imported as '${relative}'. Not to be edited: run it again to update it.\n`;
21341
+ const types = `// The types of ${name}@${version}, for '${relative}' — the installed package's own.\n${built.exports.filter((exported) => exported !== "default").length > 0 ? `export * from '${name}';\n` : ""}${built.exports.includes("default") ? `export { default } from '${name}';\n` : ""}`;
21342
+ const written = {
21343
+ [path.join(VENDOR_DIR, file)]: `${header}${built.code}`,
21344
+ [path.join(VENDOR_DIR, file.replace(/\.js$/, ".d.ts"))]: types
21345
+ };
21346
+ const base = path.join(root, h$1);
21347
+ if (options.dryRun) {
21348
+ sayDryRun(`plitzi functions add ${name}`, [...(await filesWouldWrite(base, Object.keys(written))).map((line) => line.replace(/^(.) /, `$1 ${h$1}/`))]);
21349
+ return;
21350
+ }
21351
+ for (const [target, text] of Object.entries(written)) {
21352
+ await fs.mkdir(path.dirname(path.join(base, target)), { recursive: true });
21353
+ await fs.writeFile(path.join(base, target), text);
21354
+ }
21355
+ console.log(`Added ${name}@${version} (${String(Math.round(bytes / 1024))} KB) as ${h$1}/${VENDOR_DIR}/${file}: import it from a file of ${h$1}/ with '${relative}' — \`functions push\` saves it with the rest.`);
21356
+ if (NODE_GLOBALS.test(built.code)) console.warn(`${name} reads globals of Node (process, Buffer, __dirname) somewhere: the functions have none, so whatever reads them fails when it runs. Try what you use with \`plitzi functions dev\`.`);
21357
+ };
21358
+ //#endregion
21107
21359
  //#region src/commands/siteOwnership.ts
21108
21360
  var NOT_ASKED = (host) => `${host} is not served from this machine, so importing it takes proof that it is yours: its domain verified on one of your Plitzi spaces. That is asked of your Plitzi account, signing in — run it again with --account to ask. A site that is not yours is not imported.`;
21109
21361
  var LOOPBACK = /^(127\.|::1$|::ffff:127\.)/;
@@ -21186,12 +21438,12 @@ var measure = async (browser, url, width, scheme, read = []) => {
21186
21438
  screen: await page.screenshot({ fullPage: true })
21187
21439
  };
21188
21440
  };
21189
- var count$2 = (amount, one, many = `${one}s`) => `${String(amount)} ${amount === 1 ? one : many}`;
21441
+ var count$3 = (amount, one, many = `${one}s`) => `${String(amount)} ${amount === 1 ? one : many}`;
21190
21442
  var summaryText = (summary, out, stale) => [
21191
21443
  chalk.green(`Imported ${summary.url} into ${out}`),
21192
- ` tokens ${count$2(summary.colours, "colour")} (${summary.dark ? "light and dark" : "no dark scheme: dark repeats light"}), ${count$2(summary.shadows, "shadow")}, ${count$2(summary.radii, "radius", "radii")}${summary.fonts.length > 0 ? `, fonts ${summary.fonts.join(", ")}` : ""}`,
21193
- ` outline ${count$2(summary.blocks, "block")} at ${summary.widths.join(", ")} px`,
21194
- ...summary.lists.length > 0 ? [` lists ${summary.lists.map((list) => `${list.file} (${count$2(list.rows, "row")})`).join(", ")}`] : [],
21444
+ ` tokens ${count$3(summary.colours, "colour")} (${summary.dark ? "light and dark" : "no dark scheme: dark repeats light"}), ${count$3(summary.shadows, "shadow")}, ${count$3(summary.radii, "radius", "radii")}${summary.fonts.length > 0 ? `, fonts ${summary.fonts.join(", ")}` : ""}`,
21445
+ ` outline ${count$3(summary.blocks, "block")} at ${summary.widths.join(", ")} px`,
21446
+ ...summary.lists.length > 0 ? [` lists ${summary.lists.map((list) => `${list.file} (${count$3(list.rows, "row")})`).join(", ")}`] : [],
21195
21447
  ` pictures ${String(summary.pictures)} in assets.json`,
21196
21448
  ...stale.length > 0 ? [chalk.yellow(` earlier ${stale.join(", ")} ${stale.length === 1 ? "was" : "were"} there before and not written this time: delete what is stale`)] : [],
21197
21449
  ` next ${path.join(out, "IMPORT.md")} says what was not carried over`
@@ -22409,7 +22661,7 @@ var projectConnectors = async (root) => {
22409
22661
  manifest: value.manifest
22410
22662
  }] : []);
22411
22663
  };
22412
- var count$1 = (entries, noun) => entries ? `${String(entries.length)} ${noun}${entries.length === 1 ? "" : "s"}` : `the space’s ${noun}s as they are`;
22664
+ var count$2 = (entries, noun) => entries ? `${String(entries.length)} ${noun}${entries.length === 1 ? "" : "s"}` : `the space’s ${noun}s as they are`;
22413
22665
  /**
22414
22666
  * What of the space would not reach Plitzi with it, said before it is sent — never left to be found on a page that
22415
22667
  * renders empty there:
@@ -22494,7 +22746,7 @@ var pushSpaceOf = async (root, connection, space, { base, force, downloads }) =>
22494
22746
  outcome: "unchanged",
22495
22747
  draft: data.draft
22496
22748
  };
22497
- console.log(chalk.green(`The space is ${space.name}’s draft now — its pages and styles, ${count$1(sent.actions, "action")}, ${count$1(sent.connectors, "connector")}.`) + chalk.dim(" Any builder open on it shows it now."));
22749
+ console.log(chalk.green(`The space is ${space.name}’s draft now — its pages and styles, ${count$2(sent.actions, "action")}, ${count$2(sent.connectors, "connector")}.`) + chalk.dim(" Any builder open on it shows it now."));
22498
22750
  return {
22499
22751
  outcome: "pushed",
22500
22752
  draft: data.draft
@@ -23522,7 +23774,108 @@ var _ = `[data-motion-enter], [data-motion-stagger] > *, [data-motion-loop] { an
23522
23774
  "[data-motion-on=\"scroll\"][data-motion-enter], [data-motion-on=\"scroll\"][data-motion-stagger] > * { animation-timeline: auto, auto !important; }"
23523
23775
  ].join("\n");
23524
23776
  //#endregion
23777
+ //#region src/commands/shotSteps.ts
23778
+ var STEP_NAMES = [
23779
+ "click",
23780
+ "type",
23781
+ "press",
23782
+ "wait",
23783
+ "wait-for",
23784
+ "shot",
23785
+ "frames"
23786
+ ];
23787
+ /** The longest a script may wait in one step: a page that needs more is waiting for something else. */
23788
+ var MAX_WAIT_MS = 3e4;
23789
+ var unquoted = (text) => text.replace(/^(["'])(.*)\1$/s, "$2");
23790
+ var count$1 = (value, at, max) => {
23791
+ const parsed = Number(value);
23792
+ return Number.isInteger(parsed) && parsed > 0 && parsed <= max ? parsed : { problem: `${at}: "${value ?? ""}" is not a whole number from 1 to ${String(max)}` };
23793
+ };
23794
+ /** A script, read into its steps — or what is wrong with it, said by its step. */
23795
+ var parseSteps = (script, every) => {
23796
+ const lines = script.split(/[;\n]/).map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("#"));
23797
+ if (lines.length === 0) return { problem: "--steps has no step: click, type, press, wait, wait-for, shot or frames, separated by \";\"" };
23798
+ const steps = [];
23799
+ for (const [index, line] of lines.entries()) {
23800
+ const at = `step ${String(index + 1)} ("${line}")`;
23801
+ const [name, ...rest] = line.split(/\s+/);
23802
+ const argument = unquoted(rest.join(" "));
23803
+ switch (name) {
23804
+ case "click":
23805
+ case "wait-for":
23806
+ if (!argument) return { problem: `${at}: ${name} needs an element — its name or a CSS selector` };
23807
+ steps.push({
23808
+ kind: name,
23809
+ target: argument
23810
+ });
23811
+ break;
23812
+ case "type": {
23813
+ const quoted = /^(["'])(.+?)\1\s+(.+)$/s.exec(rest.join(" "));
23814
+ const [target, text] = quoted ? [quoted[2], quoted[3]] : [rest[0], rest.slice(1).join(" ")];
23815
+ if (!target || text === "") return { problem: `${at}: type needs an element and the text — type <element> <text>, type ".panel textarea" hi` };
23816
+ steps.push({
23817
+ kind: "type",
23818
+ target,
23819
+ text: unquoted(text)
23820
+ });
23821
+ break;
23822
+ }
23823
+ case "press":
23824
+ if (!argument) return { problem: `${at}: press needs a key — Enter, Escape, ArrowDown…` };
23825
+ steps.push({
23826
+ kind: "press",
23827
+ key: argument
23828
+ });
23829
+ break;
23830
+ case "wait": {
23831
+ const ms = count$1(rest[0], at, MAX_WAIT_MS);
23832
+ if (typeof ms !== "number") return ms;
23833
+ steps.push({
23834
+ kind: "wait",
23835
+ ms
23836
+ });
23837
+ break;
23838
+ }
23839
+ case "shot":
23840
+ steps.push({
23841
+ kind: "shot",
23842
+ ...argument ? { label: argument } : {}
23843
+ });
23844
+ break;
23845
+ case "frames": {
23846
+ const frames = count$1(rest[0], at, 60);
23847
+ if (typeof frames !== "number") return frames;
23848
+ const apart = rest.length < 2 ? every : count$1(rest[1], at, MAX_WAIT_MS);
23849
+ if (typeof apart !== "number") return apart;
23850
+ steps.push({
23851
+ kind: "frames",
23852
+ count: frames,
23853
+ every: apart
23854
+ });
23855
+ break;
23856
+ }
23857
+ default: return { problem: `${at}: "${name}" is not a step — the steps are ${STEP_NAMES.join(", ")}` };
23858
+ }
23859
+ }
23860
+ return steps;
23861
+ };
23862
+ //#endregion
23525
23863
  //#region src/commands/shot.ts
23864
+ /**
23865
+ * `plitzi page shot`: a picture of one page of the project — and, asked, the numbers that say what a picture would.
23866
+ *
23867
+ * plitzi page shot /about --width 390 --scheme dark
23868
+ * plitzi page shot / --compare https://example.com --width 1440 # beside another site: what differs, and how
23869
+ * plitzi page shot / --frames 4 --every 500 # what moves: a marquee, an autoplay
23870
+ * plitzi page shot / --clip pricing-table # one element, scrolled to wherever it is
23871
+ * plitzi page shot / --scroll-to faq --viewport --frames 6 # the screen at that section, as it animates
23872
+ *
23873
+ * The project's server has to be running (`npm start`). `--json` answers in one object, for a tool or an agent. The
23874
+ * dev tools' badge is never in the picture; a picture of the whole page has every lazy image loaded and shows every
23875
+ * arrival that waits for the scroll as it ends — taken without scrolling, they would be holes where the sections are.
23876
+ */
23877
+ /** When a run's first picture is taken: once the page settles, or as soon as its HTML is in. */
23878
+ var SHOT_MOMENTS = ["settled", "load"];
23526
23879
  /** A region moves when more than this share of it changed between two frames: below is anti-aliasing and carets. */
23527
23880
  var MOVING_PERCENT = .5;
23528
23881
  /** How long a whole page waits for its pictures before it is taken anyway. */
@@ -23533,12 +23886,42 @@ var TEXTS_SHOWN = 12;
23533
23886
  var DRIFT_PX = 2;
23534
23887
  /** An arrival that waits for the scroll and has not had it: one waiting to be seen, or one tied to the scroll. */
23535
23888
  var WAITING_FOR_SCROLL = "[data-motion-on=\"view\"]:not([data-motion-seen]), [data-motion-on=\"scroll\"]";
23536
- /** A selector, or an element's name — what `data-plitzi-el` carries. */
23537
- var selectorOf = (target) => /^[#.[]/.test(target) ? target : `[data-plitzi-el="${target}"]`;
23538
- var framing = (view) => {
23539
- if (view.clip) return {
23889
+ var BARE = /^[\w-]+$/;
23890
+ /** A bare word: an element's name — letters, digits, `-` and `_`, as an id is written — or else a tag (`textarea`). */
23891
+ var namedBy = (target) => `[data-plitzi-el="${target}"]`;
23892
+ /** What a wait waits for: an element by name or, a bare word naming none, a tag — whichever shows first. */
23893
+ var awaited = (target) => BARE.test(target) ? `${namedBy(target)}, ${target}` : target;
23894
+ /**
23895
+ * A target found on the page: an element's name, a tag no element is named by (`textarea`), or a selector — CSS or
23896
+ * Playwright's own (`button:has-text("Orbit")`, `text=Saved`, `role=button[name="Publish"]`). With how many it finds
23897
+ * (none is one not on the page), or why it is no selector at all: Playwright throws on one it cannot read, and an
23898
+ * uncaught throw ended the command with a stack trace.
23899
+ */
23900
+ var targetOn = async (page, target) => {
23901
+ try {
23902
+ if (!BARE.test(target)) return {
23903
+ selector: target,
23904
+ found: await page.locator(target).first().count() > 0
23905
+ };
23906
+ if (await page.locator(namedBy(target)).first().count() > 0) return {
23907
+ selector: namedBy(target),
23908
+ found: true
23909
+ };
23910
+ return await page.locator(target).first().count() > 0 ? {
23911
+ selector: target,
23912
+ found: true
23913
+ } : {
23914
+ selector: namedBy(target),
23915
+ found: false
23916
+ };
23917
+ } catch (error) {
23918
+ return { problem: `${target} is neither an element's name nor a selector the page can read: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}` };
23919
+ }
23920
+ };
23921
+ var framing = (view, clip) => {
23922
+ if (clip) return {
23540
23923
  kind: "element",
23541
- selector: selectorOf(view.clip)
23924
+ selector: clip
23542
23925
  };
23543
23926
  return view.viewport || view.scrollTo ? { kind: "viewport" } : { kind: "page" };
23544
23927
  };
@@ -23559,24 +23942,45 @@ var pictureOf = async (browser, url, view, origin) => {
23559
23942
  colorScheme: view.scheme ?? "light",
23560
23943
  reducedMotion: view.reducedMotion ? "reduce" : "no-preference"
23561
23944
  });
23562
- if (!await openPage(page, url)) return { problem: `Nothing answers at ${url}.` };
23563
- if (view.waitFor) {
23564
- if (!await page.waitForSelector(selectorOf(view.waitFor), { timeout: 1e4 }).catch(() => null)) return { problem: `${view.waitFor} did not appear on ${url} within 10 s.` };
23945
+ const notSignedIn = origin && view.account && await signInAs(page, origin, view.account);
23946
+ if (notSignedIn) return { problem: `--as ${notSignedIn}.` };
23947
+ if (!(view.fromLoad ? await page.goto(url, { waitUntil: "domcontentloaded" }).catch(() => null) : await openPage(page, url))) return { problem: `Nothing answers at ${url}.${origin ? " Start the project first: npm start" : ""}` };
23948
+ const sentTo = origin ? sentElsewhere(page, new URL(url).pathname) : void 0;
23949
+ for (const target of [
23950
+ view.waitFor,
23951
+ view.scrollTo,
23952
+ view.clip
23953
+ ]) {
23954
+ const read = target === void 0 ? void 0 : await targetOn(page, target);
23955
+ if (read && "problem" in read) return read;
23565
23956
  }
23566
- for (const target of [view.scrollTo, view.clip]) if (target && !await page.waitForSelector(selectorOf(target), { timeout: 1e4 }).catch(() => null)) return { problem: `${target} is not on ${url}.` };
23567
- if (view.scrollTo) await page.locator(selectorOf(view.scrollTo)).first().scrollIntoViewIfNeeded();
23957
+ if (view.waitFor) {
23958
+ if (!await page.waitForSelector(awaited(view.waitFor), { timeout: 1e4 }).catch(() => null)) return { problem: `${view.waitFor} did not appear on ${url} within 10 s.` };
23959
+ }
23960
+ /** The element a target names, once it is there: what it is scrolled to, or pictured alone. */
23961
+ const shown = {};
23962
+ for (const target of [view.scrollTo, view.clip]) {
23963
+ if (target === void 0) continue;
23964
+ const read = await page.waitForSelector(awaited(target), { timeout: 1e4 }).catch(() => null) ? await targetOn(page, target) : void 0;
23965
+ if (!read || "problem" in read || !read.found) return { problem: `${target} is not on ${url}.` };
23966
+ shown[target] = read.selector;
23967
+ }
23968
+ if (view.scrollTo) await page.locator(shown[view.scrollTo]).first().scrollIntoViewIfNeeded();
23969
+ const how = framing(view, view.clip === void 0 ? void 0 : shown[view.clip]);
23568
23970
  await withoutDevTools(page);
23569
23971
  const settled = view.still ? await page.evaluate((selector) => document.querySelectorAll(selector).length, WAITING_FOR_SCROLL) : 0;
23570
- const whole = framing(view).kind === "page";
23972
+ const whole = how.kind === "page";
23571
23973
  const pictures = whole ? await page.evaluate(loadImages, IMAGES_BUDGET_MS) : void 0;
23572
23974
  if (whole) await page.evaluate(unrollPage, void 0);
23573
23975
  if (view.still) await page.addStyleTag({ content: _ });
23574
23976
  return {
23575
23977
  page,
23576
- png: await capture(page, framing(view)),
23978
+ how,
23979
+ png: await capture(page, how),
23577
23980
  scheme: await paintedScheme(page, view.scheme ?? "light"),
23578
23981
  settled,
23579
- unloaded: pictures?.missing ?? 0
23982
+ unloaded: pictures?.missing ?? 0,
23983
+ ...sentTo ? { sentTo } : {}
23580
23984
  };
23581
23985
  };
23582
23986
  var shiftText = (shift) => shift !== void 0 && Math.abs(shift) > DRIFT_PX ? ` (${shift > 0 ? "+" : ""}${String(shift)}px there)` : "";
@@ -23690,8 +24094,10 @@ var clickThrough = async (taken, how, clicks, { frames, every }) => {
23690
24094
  }];
23691
24095
  const clicked = [];
23692
24096
  for (const target of clicks) {
23693
- const element = taken.page.locator(selectorOf(target)).first();
23694
- if (await element.count() === 0) return { problem: `${target} is not on the page: --click takes an element's name or a CSS selector.` };
24097
+ const read = await targetOn(taken.page, target);
24098
+ if ("problem" in read) return read;
24099
+ if (!read.found) return { problem: `${target} is not on the page: --click takes an element's name, a tag or a selector.` };
24100
+ const element = taken.page.locator(read.selector).first();
23695
24101
  const before = moments[moments.length - 1].png;
23696
24102
  try {
23697
24103
  await element.click({ timeout: 3e3 });
@@ -23716,6 +24122,71 @@ var clickThrough = async (taken, how, clicks, { frames, every }) => {
23716
24122
  clicked
23717
24123
  };
23718
24124
  };
24125
+ /** What a step is, as its label says it. */
24126
+ var stepText = (step) => {
24127
+ switch (step.kind) {
24128
+ case "click":
24129
+ case "wait-for": return `${step.kind} ${step.target}`;
24130
+ case "type": return `type ${step.target}`;
24131
+ case "press": return `press ${step.key}`;
24132
+ case "wait": return `wait ${String(step.ms)} ms`;
24133
+ case "shot": return step.label ?? "shot";
24134
+ case "frames": return `frames ${String(step.count)}`;
24135
+ }
24136
+ };
24137
+ /**
24138
+ * A `--steps` script played on the page: what each step does, and every picture the script asks for — each labelled with
24139
+ * its step and the time since the first, and measured against the one before it, so "nothing changed" is said.
24140
+ */
24141
+ var playSteps = async (taken, how, steps) => {
24142
+ const { page } = taken;
24143
+ const started = Date.now();
24144
+ const moments = [{
24145
+ label: "before",
24146
+ png: taken.png
24147
+ }];
24148
+ const changes = [];
24149
+ const picture = async (label) => {
24150
+ const before = moments[moments.length - 1].png;
24151
+ const png = await capture(page, how);
24152
+ const at = `${label} · +${String(Date.now() - started)} ms`;
24153
+ moments.push({
24154
+ label: at,
24155
+ png
24156
+ });
24157
+ changes.push({
24158
+ label: at,
24159
+ changed: (await comparePictures(page, dataUrl(before), dataUrl(png))).changed
24160
+ });
24161
+ };
24162
+ for (const [index, step] of steps.entries()) try {
24163
+ if (step.kind === "click" || step.kind === "type") {
24164
+ const read = await targetOn(page, step.target);
24165
+ if ("problem" in read || !read.found) return { problem: `Step ${String(index + 1)}: ${"problem" in read ? read.problem : `${step.target} is not on the page — its name, a tag or a selector; one with spaces in quotes: type ".panel textarea" hola`}.` };
24166
+ await page.locator(read.selector).first().click({ timeout: 3e3 });
24167
+ if (step.kind === "type") {
24168
+ if (!await page.evaluate(() => {
24169
+ const focused = document.activeElement;
24170
+ return focused instanceof HTMLElement && (focused.isContentEditable || focused.matches("input, textarea, select"));
24171
+ }, void 0)) return { problem: `Step ${String(index + 1)}: clicking ${step.target} leaves no field focused, so the text would go to the page itself — name the field: type ".panel textarea" hola` };
24172
+ await page.keyboard.type(step.text);
24173
+ }
24174
+ } else if (step.kind === "press") await page.keyboard.press(step.key);
24175
+ else if (step.kind === "wait") await page.waitForTimeout(step.ms);
24176
+ else if (step.kind === "wait-for") await page.waitForSelector(awaited(step.target), { timeout: 1e4 });
24177
+ else if (step.kind === "shot") await picture(step.label ?? `step ${String(index + 1)}`);
24178
+ else for (let frame = 1; frame <= step.count; frame += 1) {
24179
+ if (frame > 1) await page.waitForTimeout(step.every);
24180
+ await picture(`frame ${String(frame)}/${String(step.count)}`);
24181
+ }
24182
+ } catch (error) {
24183
+ return { problem: `Step ${String(index + 1)} (${stepText(step)}) did not happen: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}` };
24184
+ }
24185
+ return {
24186
+ moments,
24187
+ changes
24188
+ };
24189
+ };
23719
24190
  var shot = async (route, options) => {
23720
24191
  const project = await projectHere("whose page to picture");
23721
24192
  if (!project) return;
@@ -23724,20 +24195,53 @@ var shot = async (route, options) => {
23724
24195
  fail(where.problem);
23725
24196
  return;
23726
24197
  }
24198
+ const account = options.as === void 0 ? void 0 : await checkAccount(project.root, options.as);
24199
+ if (account && "problem" in account) {
24200
+ fail(`--as ${account.problem}.`);
24201
+ return;
24202
+ }
23727
24203
  const browser = await launchBrowser(project.root);
23728
24204
  if ("problem" in browser) {
23729
24205
  fail(browser.problem);
23730
24206
  return;
23731
24207
  }
23732
24208
  const clicks = options.click ?? [];
24209
+ const script = options.steps?.startsWith("@") ? await readFile(path.resolve(options.steps.slice(1)), "utf-8").catch(() => void 0) : options.steps;
24210
+ if (options.steps !== void 0 && script === void 0) {
24211
+ await browser.close();
24212
+ fail(`${options.steps.slice(1)} cannot be read: --steps @<file> names a file of steps.`);
24213
+ return;
24214
+ }
24215
+ const steps = script === void 0 ? void 0 : parseSteps(script, options.every ?? 500);
24216
+ if (steps && "problem" in steps) {
24217
+ await browser.close();
24218
+ fail(`--steps: ${steps.problem}.`);
24219
+ return;
24220
+ }
24221
+ if (steps && (clicks.length > 0 || options.compare || (options.frames ?? 0) > 1)) {
24222
+ await browser.close();
24223
+ fail("`--steps` is the whole interaction — its own clicks and frames: leave out --click, --frames and --compare.");
24224
+ return;
24225
+ }
23733
24226
  if (options.compare && clicks.length > 0) {
23734
24227
  await browser.close();
23735
24228
  fail("`--compare` sets the page beside another site as it loads: leave out --click, or picture the click on its own.");
23736
24229
  return;
23737
24230
  }
23738
- if (options.sheet && clicks.length === 0 && (options.frames ?? 0) <= 1) {
24231
+ if (options.sheet && clicks.length === 0 && (options.frames ?? 0) <= 1 && options.steps === void 0) {
24232
+ await browser.close();
24233
+ fail("`--sheet` lays out the pictures a --click, --frames or --steps takes: with none of them, there is one picture and no sheet.");
24234
+ return;
24235
+ }
24236
+ const fromLoad = options.from === "load";
24237
+ if (fromLoad && ((options.frames ?? 0) <= 1 || clicks.length > 0 || options.compare)) {
23739
24238
  await browser.close();
23740
- fail("`--sheet` lays out the pictures a --click or --frames takes: with neither, there is one picture and no sheet.");
24239
+ fail("`--from load` starts --frames as the HTML arrives, for what plays while the page loads: give it --frames, and leave out --click and --compare.");
24240
+ return;
24241
+ }
24242
+ if (fromLoad && !options.clip && !options.scrollTo && !options.viewport) {
24243
+ await browser.close();
24244
+ fail("`--from load` pictures the screen as the page arrives, never the whole page — unrolling it waits for it to settle: add --viewport or --clip.");
23741
24245
  return;
23742
24246
  }
23743
24247
  if (options.compare && (options.clip || options.scrollTo || options.viewport)) {
@@ -23756,14 +24260,17 @@ var shot = async (route, options) => {
23756
24260
  ...options.clip ? { clip: options.clip } : {},
23757
24261
  ...options.scrollTo ? { scrollTo: options.scrollTo } : {},
23758
24262
  ...options.viewport ? { viewport: true } : {},
24263
+ ...options.presence ? { presence: true } : {},
24264
+ ...account ? { account } : {},
24265
+ ...fromLoad ? { fromLoad } : {},
23759
24266
  still: !options.clip && !options.scrollTo && !options.viewport && (options.frames ?? 0) <= 1 && clicks.length === 0
23760
24267
  };
23761
- const how = framing(view);
23762
24268
  const taken = await pictureOf(browser, `${where.origin}${pathname}`, view, where.origin);
23763
24269
  if ("problem" in taken) {
23764
- fail(`${taken.problem} Start the project first: npm start`);
24270
+ fail(taken.problem);
23765
24271
  return;
23766
24272
  }
24273
+ const { how } = taken;
23767
24274
  const name = pathname === "/" ? "home" : pathname.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "");
23768
24275
  const out = path.resolve(project.root, options.out ?? `tmp/shots/${name}-${String(view.width)}-${taken.scheme}.png`);
23769
24276
  const base = out.replace(/\.png$/, "");
@@ -23776,7 +24283,8 @@ var shot = async (route, options) => {
23776
24283
  scheme: taken.scheme,
23777
24284
  out,
23778
24285
  ...taken.settled > 0 ? { settled: taken.settled } : {},
23779
- ...taken.unloaded > 0 ? { unloaded: taken.unloaded } : {}
24286
+ ...taken.unloaded > 0 ? { unloaded: taken.unloaded } : {},
24287
+ ...taken.sentTo ? { sentTo: taken.sentTo } : {}
23780
24288
  };
23781
24289
  if (options.compare) {
23782
24290
  const other = new URL(options.compare);
@@ -23790,7 +24298,15 @@ var shot = async (route, options) => {
23790
24298
  const frames = options.frames ?? 0;
23791
24299
  const every = options.every ?? 500;
23792
24300
  let moments = [];
23793
- if (clicks.length > 0) {
24301
+ if (steps) {
24302
+ const played = await playSteps(taken, how, steps);
24303
+ if ("problem" in played) {
24304
+ fail(played.problem);
24305
+ return;
24306
+ }
24307
+ moments = played.moments;
24308
+ report.steps = played.changes;
24309
+ } else if (clicks.length > 0) {
23794
24310
  const played = await clickThrough(taken, how, clicks, {
23795
24311
  frames,
23796
24312
  every
@@ -23808,7 +24324,7 @@ var shot = async (route, options) => {
23808
24324
  pictures.push(await capture(taken.page, how));
23809
24325
  }
23810
24326
  moments = pictures.map((png, frame) => ({
23811
- label: frame === 0 ? "before" : `+${String(frame * every)} ms`,
24327
+ label: frame === 0 ? fromLoad ? "HTML in" : "before" : `+${String(frame * every)} ms`,
23812
24328
  png
23813
24329
  }));
23814
24330
  const diffs = [];
@@ -23832,7 +24348,7 @@ var shot = async (route, options) => {
23832
24348
  })));
23833
24349
  report.sheet = `${base}-sheet.png`;
23834
24350
  await writeFile(report.sheet, Buffer.from(sheet.replace(/^data:image\/png;base64,/, ""), "base64"));
23835
- } else if (moments.length > 1 && clicks.length > 0) {
24351
+ } else if (moments.length > 1 && (clicks.length > 0 || steps)) {
23836
24352
  report.pictures = [];
23837
24353
  for (const [index, moment] of moments.slice(1).entries()) {
23838
24354
  const file = `${base}-${String(index + 1)}.png`;
@@ -23848,9 +24364,11 @@ var shot = async (route, options) => {
23848
24364
  return;
23849
24365
  }
23850
24366
  console.log(out);
24367
+ if (report.sentTo) console.log(chalk.yellow(account ? `${pathname} sent the browser to ${report.sentTo}, signed in as ${account.username}: the picture is of ${report.sentTo} — that account may not see ${pathname}` : `${pathname} sent the browser to ${report.sentTo}: the picture is of ${report.sentTo}. A page for signed-in visitors? \`--as <username>\` signs in first, the password in ${CHECK_PASSWORD_ENV} in .env`));
23851
24368
  if (report.settled) console.log(chalk.dim(`${String(report.settled)} arrival${report.settled === 1 ? "" : "s"} waiting for the scroll shown as they end — --scroll-to <id> --viewport --frames 4 shows one arriving`));
23852
24369
  if (report.unloaded) console.log(chalk.yellow(`${String(report.unloaded)} picture(s) had not loaded when the page was taken`));
23853
24370
  if (report.compare) console.log(compareText(report.compare).join("\n"));
24371
+ for (const { label, changed } of report.steps ?? []) console.log(changed > 0 ? `${label}: ${String(changed)}% of the picture changed` : chalk.yellow(`${label}: nothing changed since the picture before`));
23854
24372
  for (const { target, changed } of report.clicked ?? []) console.log(changed > 0 ? `clicked ${target}: ${String(changed)}% of the picture changed` : chalk.yellow(`clicked ${target}: nothing changed — the click did nothing to see`));
23855
24373
  if (report.sheet) console.log(`sheet: ${report.sheet}`);
23856
24374
  for (const picture of report.pictures ?? []) console.log(`${picture.label}: ${picture.out}`);
@@ -23961,30 +24479,39 @@ var verify = async (options) => {
23961
24479
  };
23962
24480
  var NOT_STARTED = "start it (npm start) and verify again, or --no-pages to leave them";
23963
24481
  var pagesChecked = async (project) => {
23964
- const paths = await staticPaths(project.root);
23965
- if ("problem" in paths) return paths;
23966
- const options = {
23967
- widths: WIDTHS,
23968
- asked: { state: false },
23969
- ssr: false
24482
+ const found = await staticPaths(project.root);
24483
+ if ("problem" in found) return found;
24484
+ const { paths, schemes } = found;
24485
+ const inEveryTheme = async (routes, account) => {
24486
+ const all = [];
24487
+ for (const scheme of schemes) {
24488
+ const reports = await checkRoutes(project, routes, {
24489
+ widths: WIDTHS,
24490
+ asked: { state: false },
24491
+ ssr: false,
24492
+ scheme,
24493
+ ...account ? { account } : {}
24494
+ });
24495
+ if ("problem" in reports) return { problem: `${reports.problem} — ${NOT_STARTED}` };
24496
+ all.push(...reports);
24497
+ }
24498
+ return all;
23970
24499
  };
23971
- const reports = await checkRoutes(project, paths, options);
23972
- if ("problem" in reports) return { problem: `${reports.problem} — ${NOT_STARTED}` };
24500
+ const reports = await inEveryTheme(paths);
24501
+ if ("problem" in reports) return reports;
23973
24502
  const sentAway = [...new Set(reports.filter(redirected).map((report) => report.path))];
23974
- if (sentAway.length === 0) return summary(reports);
24503
+ if (sentAway.length === 0) return summary(schemes, reports);
23975
24504
  const account = await checkAccount(project.root);
23976
- if (!account || "problem" in account) return summary(reports, account ?? { none: true });
23977
- const signedIn = await checkRoutes(project, sentAway, {
23978
- ...options,
23979
- account
23980
- });
23981
- if ("problem" in signedIn) return { problem: `${signedIn.problem} — ${NOT_STARTED}` };
23982
- return summary([...reports.filter((report) => !sentAway.includes(report.path)), ...signedIn], {
24505
+ if (!account || "problem" in account) return summary(schemes, reports, account ?? { none: true });
24506
+ const signedIn = await inEveryTheme(sentAway, account);
24507
+ if ("problem" in signedIn) return signedIn;
24508
+ return summary(schemes, [...reports.filter((report) => !sentAway.includes(report.path)), ...signedIn], {
23983
24509
  as: account.username,
23984
24510
  checks: signedIn.filter((report) => !unchecked(report)).length
23985
24511
  });
23986
24512
  };
23987
- var summary = (reports, signedIn) => ({
24513
+ var summary = (schemes, reports, signedIn) => ({
24514
+ schemes,
23988
24515
  passing: reports.filter((report) => report.ok).length,
23989
24516
  failing: reports.filter((report) => !report.ok && !unchecked(report)),
23990
24517
  unchecked: [...new Set(reports.filter(unchecked).map((report) => report.path))],
@@ -24003,7 +24530,7 @@ var verifyText = (results, pages) => [...results.flatMap((result) => result.ok ?
24003
24530
  ...(result.said ?? []).map((line) => ` ${line}`),
24004
24531
  ...result.fix ? [` → ${result.fix}`] : []
24005
24532
  ]), ...pages === void 0 ? [chalk.yellow("- pages not checked: --no-pages")] : "waiting" in pages ? [chalk.yellow(`- not run until ${pages.after} passes: ${pages.waiting.join(", ")} (--keep-going runs them anyway)`)] : "problem" in pages ? [chalk.red(`✗ pages — ${pages.problem}`)] : [
24006
- pages.failing.length === 0 ? chalk.green(`✓ pages — ${String(pages.passing)} checks at ${WIDTHS.join(" and ")} px${pages.signedIn && "as" in pages.signedIn ? `, ${String(pages.signedIn.checks)} of them signed in as ${pages.signedIn.as}` : ""}`) : chalk.red(`✗ pages — ${String(pages.failing.length)} of ${String(pages.passing + pages.failing.length)} checks`),
24533
+ pages.failing.length === 0 ? chalk.green(`✓ pages — ${String(pages.passing)} checks at ${WIDTHS.join(" and ")} px, ${pages.schemes.join(" and ")}${pages.signedIn && "as" in pages.signedIn ? `, ${String(pages.signedIn.checks)} of them signed in as ${pages.signedIn.as}` : ""}`) : chalk.red(`✗ pages — ${String(pages.failing.length)} of ${String(pages.passing + pages.failing.length)} checks`),
24007
24534
  ...pages.failing.map((report) => reportText$2(report, { state: false })),
24008
24535
  ...pages.unchecked.length > 0 ? [chalk.yellow(uncheckedText(pages))] : []
24009
24536
  ]].join("\n");
@@ -27185,14 +27712,14 @@ program.command("login").description("Sign in, in your browser. The session is k
27185
27712
  program.command("logout").description("Sign out: the session is revoked on the platform and forgotten here").action(() => logout());
27186
27713
  program.command("whoami").description("Who the CLI is signed in as, and the space it works in").option("--json", "One object: { api, user, space }, for a tool or an agent").option(...API_OPTION).action((options) => whoami(options));
27187
27714
  spaceGroup.command("use").description("Choose the space to work in, in your browser. One at a time: it replaces the one chosen before").option(...API_OPTION).action((options) => space$2(options));
27188
- pageGroup.command("check").argument("[paths...]", "The pages: /about /writers, in one browser. The home page when left out").description("Whether a page of the running project is whole, in text: every element on screen, no broken image, no console error, no failed flow").addOption(widthsOption("The widths to check it at, separated by commas", [1440, 390])).addOption(schemeOption()).option("--state", "Also what the page holds: its state, and every source by name").option("--element <id>", "Also one element: what it reads, its own state, whether it is on screen").option("--ssr", "Also the HTML the server sent against the hydrated page: what a server provider sent late").option("--click <id>", "Click one element once the page settles, and say what it changed — or that nothing did").addOption(new Option("--fill <id=value>", "Fill a field before the click, as a visitor types in it — once per field; a box takes true or false").argParser(fill)).option("--as <username>", "Sign in first as this account, through the server’s /auth routes — the password from PLITZI_CHECK_PASSWORD, in .env").option("--json", "One object per page and width, for a tool or an agent").action((paths, options) => check(paths, options));
27715
+ pageGroup.command("check").argument("[paths...]", "The pages: /about /writers, in one browser. The home page when left out").description("Whether a page of the running project is whole, in text: every element on screen, no broken image, no console error, no failed flow").addOption(widthsOption("The widths to check it at, separated by commas", [1440, 390])).addOption(schemeOption()).option("--state", "Also what the page holds: its state, and every source by name").option("--element <id>", "Also one element: what it reads, its own state, whether it is on screen").option("--ssr", "Also the HTML the server sent against the hydrated page: what a server provider sent late").option("--click <id>", "Click one element once the page settles, and say what it changed — or that nothing did").addOption(new Option("--fill <id=value>", "Fill a field before the click, as a visitor types in it — once per field; a box takes true or false").argParser(fill)).option("--as <username>", "Sign in first as this account, through the server’s /auth routes — the password from PLITZI_CHECK_PASSWORD, in .env").option("--presence", "Take part in the page’s realtime channels as a visitor does — arrive, announce, send. Otherwise it only watches them").option("--json", "One object per page and width, for a tool or an agent").action((paths, options) => check(paths, options));
27189
27716
  spaceGroup.command("fix").description("What authoring would fix in the space, as edits to your source: shown as a diff; with --write written, formatted and checked").option("--write", "Write the edits — kept only when the space authors with each fix gone and no problem added").option("--json", "The plan as one object, for a tool or an agent").action((options) => fix(options));
27190
27717
  pageGroup.command("import").argument("<url>", "The page to start from: https://example.com/pricing").description("A page of a site you verified, as a place to start writing from: its tokens, the outline of its blocks per breakpoint, its lists as JSON — never its words").option("-o, --out <dir>", "Where it is written, inside the project", "src/imported").addOption(widthsOption("The widths it is measured at: the widest is desktop, then tablet and mobile", [
27191
27718
  1440,
27192
27719
  768,
27193
27720
  390
27194
27721
  ])).option("-f, --force", "Write over what --out already holds").option("--account", "A site not served from this machine: ask your Plitzi account whether one of your spaces verified its domain (signs in)").option("--json", "One object, for a tool or an agent").option(...API_OPTION).option(...DRY_RUN_OPTION).action((url, options) => importPage(url, options));
27195
- pageGroup.command("shot").argument("[path]", "The page: /about. The home page when left out").description("A picture of one page of the running project — and, asked, how it differs from another or what moves").addOption(new Option("--width <px>", "The viewport width").argParser(width).default(1280)).addOption(new Option("--height <px>", "The viewport height (the picture is the whole page unless --viewport, --scroll-to or --clip)").argParser(positiveInteger).default(800)).addOption(schemeOption()).option("-o, --out <file>", "Where the picture goes. tmp/shots/<page>-<width>-<scheme>.png by default").option("--compare <url>", "Another site: the same page there, side by side, and how much differs by section").addOption(new Option("--frames <n>", "Pictures taken one after another: which sections move (a marquee, an autoplay)").argParser(positiveInteger)).addOption(new Option("--every <ms>", "How far apart --frames are taken").argParser(positiveInteger).default(500)).option("--wait-for <element>", "An element to wait for first: its name (data-plitzi-el) or a CSS selector").option("--clip <element>", "Only this element — its name or a CSS selector — scrolled to wherever it is").option("--scroll-to <element>", "The page scrolled until this element is in view; the picture is the screen there").option("--viewport", "Only what fits the viewport, not the whole page").option("--click <element...>", "Click these in order once the page settles — each its name or a CSS selector, inside a plugin too — picturing what each changed (--frames pictures --every ms apart after each)").option("--sheet", "Every picture taken — before, after each click, each frame — on one contact sheet").option("--reduced-motion", "As a visitor who asked for less motion").option("--json", "One object, for a tool or an agent").action((path, options) => shot(path, options));
27722
+ pageGroup.command("shot").argument("[path]", "The page: /about. The home page when left out").description("A picture of one page of the running project — and, asked, how it differs from another or what moves").addOption(new Option("--width <px>", "The viewport width").argParser(width).default(1280)).addOption(new Option("--height <px>", "The viewport height (the picture is the whole page unless --viewport, --scroll-to or --clip)").argParser(positiveInteger).default(800)).addOption(schemeOption()).option("-o, --out <file>", "Where the picture goes. tmp/shots/<page>-<width>-<scheme>.png by default").option("--compare <url>", "Another site: the same page there, side by side, and how much differs by section").addOption(new Option("--frames <n>", "Pictures taken one after another: which sections move (a marquee, an autoplay)").argParser(positiveInteger)).addOption(new Option("--every <ms>", "How far apart --frames are taken").argParser(positiveInteger).default(500)).addOption(new Option("--from <moment>", "When --frames start: once the page settles, or as soon as its HTML is in (load) — what plays as it arrives").choices([...SHOT_MOMENTS]).default("settled")).option("--wait-for <element>", "An element to wait for first: its name (data-plitzi-el) or a CSS selector").option("--clip <element>", "Only this element — its name or a CSS selector — scrolled to wherever it is").option("--scroll-to <element>", "The page scrolled until this element is in view; the picture is the screen there").option("--viewport", "Only what fits the viewport, not the whole page").option("--click <element...>", "Click these in order once the page settles — each its name or a CSS selector, inside a plugin too — picturing what each changed (--frames pictures --every ms apart after each)").option("--sheet", "Every picture taken — before, after each click, each frame — on one contact sheet").option("--steps <script>", "An interaction, step by step: \"click <el>; type <el> <text>; press <key>; wait <ms>; wait-for <el>; shot [label]; frames <n> [ms]\" — or @file with one per line").option("--reduced-motion", "As a visitor who asked for less motion").option("--as <username>", "Sign in first as this account, through the server’s /auth routes — the password from PLITZI_CHECK_PASSWORD, in .env").option("--presence", "Take part in the page’s realtime channels as a visitor does — arrive, announce, send. Otherwise it only watches them").option("--json", "One object, for a tool or an agent").action((path, options) => shot(path, options));
27196
27723
  program.command("verify").description("Whether the project is left passing: author, lints, types, format and every page, only the problems said").option("--no-pages", "Leave the pages out — said as such").option("--keep-going", "Run every check even after one fails — by default the first failure ends the run").option("--json", "One object, for a tool or an agent").action((options) => verify(options));
27197
27724
  elementGroup.command("where").argument("<query>", "An element’s id, a class it wears, or words it shows: hero-cta, navLink, \"Get started\"").description("Where the project’s code writes an element: the file, the line and the call itself").addOption(new Option("--by <reading>", "Read the query one way only, not the first of id, class and words that matches").choices(WHERE_READINGS)).option("--json", "One object, for a tool or an agent").action((query, options) => where(query, options));
27198
27725
  elementGroup.command("edit").argument("<id>", "The element, by its id: hero-cta").description("Change an element’s attributes in the call that writes it, then check the space still authors").option("--set <key=value...>", "An attribute to write: content=\"Start free\", required=true").option("--remove <key...>", "An attribute to take out").option("--every", "The call writes other elements too, and the change is for every one").option("--json", "One object, for a tool or an agent").action((id, options) => edit(id, options));
@@ -27210,6 +27737,7 @@ pluginGroup.command("upload").argument("[zip]", "The zip plitzi plugin pack buil
27210
27737
  var functions = program.command("functions").description(`The space’s own server code: ${h$1}/ in this project is a working copy of it`);
27211
27738
  functions.command("pull").description(`Write the space’s functions into ${h$1}/ — refused when that would overwrite what is not pushed`).option("-f, --force", "Overwrite what is not pushed, or a copy of another space").option(...API_OPTION).option(...DRY_RUN_OPTION).action((options) => pullFunctions(options));
27212
27739
  functions.command("push").description(`Save ${h$1}/ as the space’s draft: built and checked on the platform, refused if it moved on since`).option(...API_OPTION).option(...DRY_RUN_OPTION).action((options) => pushFunctions(options));
27740
+ functions.command("add").argument("<package>", "An npm package the project installed — ical.js, @scope/name").description(`Bundle a package into ${h$1}/vendor/ for the functions to import, as a function carries its dependencies — web APIs only`).option(...DRY_RUN_OPTION).action((name, options) => addFunctionsPackage(name, options));
27213
27741
  functions.command("try").argument("<task>", "The task, <namespace>.<action>").description("Run one task of the draft in the sandbox: its value, what it logged, and why it failed").option("--params <json>", "Its params, as a JSON object").option(...API_OPTION).action((task, options) => tryFunction(task, options));
27214
27742
  functions.command("dev").argument("<task>", "The task, <namespace>.<action>").description(`Run one task from ${h$1}/ on this machine, as the platform runs it — nothing reaches the space`).option("--params <json>", "Its params, as a JSON object").option("-w, --watch", `Run it again every time a file of ${h$1}/ is saved`).action((task, options) => devFunction(task, options));
27215
27743
  runtime.command("push").description("Pack this project’s runtime module and keep it as the space’s draft runtime").option("--entry <path>", "The module whose default export is defineRuntime(…)", "src/runtime/index.ts").option(...API_OPTION).option(...DRY_RUN_OPTION).action((options) => pushRuntime(options));