@plitzi/cli 0.38.9 → 0.38.10

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
@@ -1354,7 +1354,7 @@ var n$3 = [
1354
1354
  ".otf"
1355
1355
  ];
1356
1356
  var r$5 = () => Object.fromEntries(n$3.map((e) => [e, "dataurl"]));
1357
- var i$5 = /\?(raw|inline)$/;
1357
+ var i$4 = /\?(raw|inline)$/;
1358
1358
  var a$5 = {
1359
1359
  ".wasm": "application/wasm",
1360
1360
  ".js": "text/javascript",
@@ -1371,8 +1371,8 @@ var a$5 = {
1371
1371
  var o$4 = () => ({
1372
1372
  name: "plitzi-import-queries",
1373
1373
  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, ""), {
1374
+ n.onResolve({ filter: i$4 }, async (e) => {
1375
+ let t = i$4.exec(e.path)?.[1] ?? "raw", r = await n.resolve(e.path.replace(i$4, ""), {
1376
1376
  kind: e.kind,
1377
1377
  importer: e.importer,
1378
1378
  resolveDir: e.resolveDir
@@ -2656,7 +2656,7 @@ ${code(".claude/skills/plitzi-cli/SKILL.md")} first: ${code("plitzi plugin add")
2656
2656
 
2657
2657
  What you leave behind is the next reader's problem — the user's, or the next agent's. Before calling a change done:
2658
2658
 
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.
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. 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
2660
  - **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
2661
  - **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
2662
  - **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.
@@ -2908,6 +2908,8 @@ var serverOptionsModule = (local) => `import type { ProjectServerOptions } from
2908
2908
  *
2909
2909
  * - \`images: { domains: ['images.example.com'] }\` — pictures from those hosts resized here, with \`sharp\` installed.
2910
2910
  ${local ? ACTION_OPTIONS_DOC : ""} * - \`rsc: { elementTimeoutMs }\` — how long a section resolved on the server is waited for: 5 s by default.
2911
+ * - \`devMode\` — the development build, the dev tools, a public action's trace: on unless \`NODE_ENV\` is \`production\`.
2912
+ * \`devMode: false\` keeps them off wherever the server is started, \`NODE_ENV\` or not. \`devReload\` follows it.
2911
2913
  */
2912
2914
  export const serverOptions: ProjectServerOptions = {};
2913
2915
  `;
@@ -4425,15 +4427,15 @@ var __exportAll = (all, no_symbols) => {
4425
4427
  if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
4426
4428
  return target;
4427
4429
  };
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})`;
4430
+ var e$43 = "[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*";
4431
+ var t$63 = `(?:${e$43}|\\d+)`;
4432
+ var n$47 = `\\[\\s*(?:\\d+|'[^']*'|"[^"]*"|${e$43}(?:\\.${t$63})*)\\s*\\]`;
4433
+ var r$36 = `${`${e$43}(?:${n$47})*(?:\\??\\.${t$63}(?:${n$47})*)*`}(?:\\s*\\?\\?\\s*[^|{}]+?)?(?:\\s*\\|\\s*[a-zA-Z_]+(?:\\([^)]*\\))?)*`;
4434
+ var i$29 = `\\{\\{\\s*${r$36}\\s*\\}\\}`;
4435
+ var a$24 = `(?:${`\\{\\{\\{\\s*${r$36}\\s*\\}\\}\\}`}|${i$29})`;
4434
4436
  new RegExp(a$24, "g");
4435
4437
  RegExp(`^${a$24}$`);
4436
- var e$41 = ["smooth", "auto"];
4438
+ var e$42 = ["smooth", "auto"];
4437
4439
  var t$61 = [
4438
4440
  "start",
4439
4441
  "center",
@@ -4445,7 +4447,7 @@ var r$34 = {
4445
4447
  type: "select",
4446
4448
  description: "How it moves: \"smooth\" glides there, \"auto\" jumps.",
4447
4449
  default: "smooth",
4448
- options: [...e$41]
4450
+ options: [...e$42]
4449
4451
  };
4450
4452
  var a$22 = {
4451
4453
  setState: {
@@ -4561,7 +4563,7 @@ var a$22 = {
4561
4563
  }
4562
4564
  }
4563
4565
  };
4564
- var e$40 = {
4566
+ var e$41 = {
4565
4567
  onLoad: {
4566
4568
  action: "onLoad",
4567
4569
  title: "On Load",
@@ -4731,7 +4733,7 @@ var e$40 = {
4731
4733
  params: {}
4732
4734
  }
4733
4735
  };
4734
- var e$39 = () => (e) => e;
4736
+ var e$40 = () => (e) => e;
4735
4737
  var t$60 = () => (e) => e;
4736
4738
  var t$59 = t$60()({
4737
4739
  type: "blockHtml",
@@ -4964,13 +4966,13 @@ var n$44 = {
4964
4966
  var r$33 = t$60()({
4965
4967
  type: "button",
4966
4968
  attributeValues: {
4967
- subType: e$39()([
4969
+ subType: e$40()([
4968
4970
  "button",
4969
4971
  "submit",
4970
4972
  "reset"
4971
4973
  ]),
4972
- contentPlacement: e$39()(["before", "after"]),
4973
- iconPlacement: e$39()(["before", "after"])
4974
+ contentPlacement: e$40()(["before", "after"]),
4975
+ iconPlacement: e$40()(["before", "after"])
4974
4976
  },
4975
4977
  content: {
4976
4978
  attributes: {
@@ -5038,7 +5040,7 @@ var r$33 = t$60()({
5038
5040
  });
5039
5041
  var n$43 = t$60()({
5040
5042
  type: "dropdown",
5041
- attributeValues: { popupPlacement: e$39()([
5043
+ attributeValues: { popupPlacement: e$40()([
5042
5044
  "left",
5043
5045
  "right",
5044
5046
  "top",
@@ -5169,7 +5171,7 @@ var t$54 = t$60()({
5169
5171
  });
5170
5172
  var n$42 = t$60()({
5171
5173
  type: "heading",
5172
- attributeValues: { subType: e$39()([
5174
+ attributeValues: { subType: e$40()([
5173
5175
  "h1",
5174
5176
  "h2",
5175
5177
  "h3",
@@ -5291,20 +5293,20 @@ var n$42 = t$60()({
5291
5293
  var n$41 = t$60()({
5292
5294
  type: "link",
5293
5295
  attributeValues: {
5294
- mode: e$39()([
5296
+ mode: e$40()([
5295
5297
  "page",
5296
5298
  "internal",
5297
5299
  "external"
5298
5300
  ]),
5299
- target: e$39()([
5301
+ target: e$40()([
5300
5302
  "self",
5301
5303
  "blank",
5302
5304
  "parent",
5303
5305
  "top"
5304
5306
  ]),
5305
- contentPlacement: e$39()(["before", "after"]),
5306
- iconPlacement: e$39()(["before", "after"]),
5307
- current: e$39()(["page", "section"])
5307
+ contentPlacement: e$40()(["before", "after"]),
5308
+ iconPlacement: e$40()(["before", "after"]),
5309
+ current: e$40()(["page", "section"])
5308
5310
  },
5309
5311
  content: {
5310
5312
  attributes: {
@@ -5476,7 +5478,7 @@ var t$52 = t$60()({
5476
5478
  });
5477
5479
  var n$40 = t$60()({
5478
5480
  type: "richText",
5479
- attributeValues: { format: e$39()([
5481
+ attributeValues: { format: e$40()([
5480
5482
  "html",
5481
5483
  "markdown",
5482
5484
  "text"
@@ -5613,7 +5615,7 @@ var n$39 = {
5613
5615
  };
5614
5616
  var r$32 = t$60()({
5615
5617
  type: "themeToggle",
5616
- attributeValues: { subType: e$39()(["switch", "segmented"]) },
5618
+ attributeValues: { subType: e$40()(["switch", "segmented"]) },
5617
5619
  triggers: { onThemeChange: {
5618
5620
  action: "onThemeChange",
5619
5621
  title: "On Theme Change",
@@ -5679,7 +5681,7 @@ var r$32 = t$60()({
5679
5681
  });
5680
5682
  var n$38 = t$60()({
5681
5683
  type: "form",
5682
- attributeValues: { method: e$39()(["get", "post"]) },
5684
+ attributeValues: { method: e$40()(["get", "post"]) },
5683
5685
  sourceType: "apiContainer",
5684
5686
  triggers: { onSubmit: {
5685
5687
  action: "onSubmit",
@@ -5813,7 +5815,7 @@ var r$31 = {
5813
5815
  ...n$37,
5814
5816
  "line-height": "20px"
5815
5817
  };
5816
- var i$25 = {
5818
+ var i$26 = {
5817
5819
  display: "flex",
5818
5820
  cursor: "pointer",
5819
5821
  "font-weight": "600",
@@ -5823,7 +5825,7 @@ var i$25 = {
5823
5825
  "user-select": "none"
5824
5826
  };
5825
5827
  var a$21 = {
5826
- ...i$25,
5828
+ ...i$26,
5827
5829
  "margin-bottom": "4px"
5828
5830
  };
5829
5831
  var o$23 = {
@@ -5857,7 +5859,7 @@ var l$13 = {
5857
5859
  "font-size": "inherit",
5858
5860
  "box-shadow": "none"
5859
5861
  };
5860
- var u$13 = {
5862
+ var u$14 = {
5861
5863
  height: "100%",
5862
5864
  width: "100%",
5863
5865
  display: "block",
@@ -5941,7 +5943,7 @@ var m$7 = (e, t, { field: n, icon: r } = {}) => ({
5941
5943
  var h$4 = { field: l$13 };
5942
5944
  var g$4 = t$60()({
5943
5945
  type: "formControl",
5944
- attributeValues: { subType: e$39()([
5946
+ attributeValues: { subType: e$40()([
5945
5947
  "text",
5946
5948
  "number",
5947
5949
  "email",
@@ -5996,7 +5998,7 @@ var g$4 = t$60()({
5996
5998
  definition: {
5997
5999
  label: "Form Control",
5998
6000
  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`.",
6001
+ 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
6002
  bindings: {},
6001
6003
  styleSelectors: {
6002
6004
  base: "",
@@ -6051,7 +6053,7 @@ var g$4 = t$60()({
6051
6053
  color: "inherit",
6052
6054
  resize: "vertical"
6053
6055
  }),
6054
- select: m$7("Form Control Select", f$10, { field: u$13 }),
6056
+ select: m$7("Form Control Select", f$10, { field: u$14 }),
6055
6057
  checkbox: {
6056
6058
  name: "Form Control Checkbox",
6057
6059
  displayMode: "desktop",
@@ -6063,7 +6065,7 @@ var g$4 = t$60()({
6063
6065
  "margin-left": "0px",
6064
6066
  "margin-right": "4px"
6065
6067
  } },
6066
- label: { default: i$25 },
6068
+ label: { default: i$26 },
6067
6069
  requiredMark: { default: s$20 },
6068
6070
  error: { default: o$23 }
6069
6071
  }
@@ -6075,7 +6077,7 @@ var g$4 = t$60()({
6075
6077
  base: { default: {} },
6076
6078
  input: { default: p$11 },
6077
6079
  label: { default: {
6078
- ...i$25,
6080
+ ...i$26,
6079
6081
  "align-items": "center"
6080
6082
  } },
6081
6083
  requiredMark: { default: s$20 },
@@ -6089,7 +6091,7 @@ var g$4 = t$60()({
6089
6091
  });
6090
6092
  var n$36 = t$60()({
6091
6093
  type: "layoutContainer",
6092
- attributeValues: { subType: e$39()([
6094
+ attributeValues: { subType: e$40()([
6093
6095
  "div",
6094
6096
  "header",
6095
6097
  "footer",
@@ -6221,6 +6223,11 @@ var t$49 = t$60()({
6221
6223
  });
6222
6224
  var t$48 = t$60()({
6223
6225
  type: "page",
6226
+ serverTemplates: [
6227
+ "seoPageTitle",
6228
+ "seoPageDescription",
6229
+ "notFound"
6230
+ ],
6224
6231
  triggers: { onPageLoad: {
6225
6232
  action: "onPageLoad",
6226
6233
  title: "On Page Load",
@@ -6388,12 +6395,12 @@ var t$46 = t$60()({
6388
6395
  var n$35 = t$60()({
6389
6396
  type: "image",
6390
6397
  attributeValues: {
6391
- fetchPriority: e$39()([
6398
+ fetchPriority: e$40()([
6392
6399
  "high",
6393
6400
  "low",
6394
6401
  "auto"
6395
6402
  ]),
6396
- loadMode: e$39()([
6403
+ loadMode: e$40()([
6397
6404
  "auto",
6398
6405
  "eager",
6399
6406
  "lazy"
@@ -6542,7 +6549,7 @@ var t$44 = t$60()({
6542
6549
  var n$34 = t$60()({
6543
6550
  type: "apiContainer",
6544
6551
  attributeValues: {
6545
- subType: e$39()([
6552
+ subType: e$40()([
6546
6553
  "",
6547
6554
  "div",
6548
6555
  "header",
@@ -6555,25 +6562,26 @@ var n$34 = t$60()({
6555
6562
  "address",
6556
6563
  "figure"
6557
6564
  ]),
6558
- method: e$39()([
6565
+ method: e$40()([
6559
6566
  "get",
6560
6567
  "post",
6561
6568
  "put",
6562
6569
  "delete",
6563
6570
  "patch"
6564
6571
  ]),
6565
- credentials: e$39()([
6572
+ credentials: e$40()([
6566
6573
  "include",
6567
6574
  "omit",
6568
6575
  "same-origin"
6569
6576
  ]),
6570
- pagination: e$39()([
6577
+ pagination: e$40()([
6571
6578
  "none",
6572
6579
  "url",
6573
6580
  "append"
6574
6581
  ])
6575
6582
  },
6576
6583
  sourceType: "apiContainer",
6584
+ serverTemplates: ["notFound"],
6577
6585
  triggers: {
6578
6586
  onApiError: {
6579
6587
  action: "onApiError",
@@ -6715,7 +6723,7 @@ var n$34 = t$60()({
6715
6723
  });
6716
6724
  var n$33 = t$60()({
6717
6725
  type: "channel",
6718
- attributeValues: { subType: e$39()([
6726
+ attributeValues: { subType: e$40()([
6719
6727
  "",
6720
6728
  "div",
6721
6729
  "section",
@@ -6835,6 +6843,49 @@ var n$33 = t$60()({
6835
6843
  }
6836
6844
  });
6837
6845
  var t$43 = t$60()({
6846
+ type: "carouselTrack",
6847
+ ancestorType: "carousel",
6848
+ content: {
6849
+ attributes: {},
6850
+ definition: {
6851
+ label: "Carousel Track",
6852
+ type: "carouselTrack",
6853
+ description: "Where a carousel shows its slides: what is inside is one slide, rendered once per item.",
6854
+ items: [],
6855
+ bindings: {},
6856
+ styleSelectors: { base: "" },
6857
+ initialState: { visibility: !0 }
6858
+ },
6859
+ builder: {
6860
+ canDelete: !0,
6861
+ canSelect: !0,
6862
+ canDragDrop: !0,
6863
+ canMove: !0,
6864
+ canSnippet: !1,
6865
+ itemsAllowed: [],
6866
+ itemsNotAllowed: []
6867
+ },
6868
+ market: {
6869
+ category: "structure",
6870
+ owner: "Plitzi",
6871
+ verified: !0,
6872
+ license: "MIT",
6873
+ website: "https://plitzi.com",
6874
+ backgroundColor: "#4422ee",
6875
+ icon: "fa-solid fa-film"
6876
+ },
6877
+ defaultStyle: {
6878
+ name: "Carousel Track",
6879
+ displayMode: "desktop",
6880
+ style: { base: { default: {
6881
+ position: "relative",
6882
+ overflow: "hidden"
6883
+ } } }
6884
+ },
6885
+ settings: {}
6886
+ }
6887
+ });
6888
+ var t$42 = t$60()({
6838
6889
  type: "carousel",
6839
6890
  sourceType: "carousel",
6840
6891
  triggers: { onChange: {
@@ -6936,53 +6987,10 @@ var t$43 = t$60()({
6936
6987
  },
6937
6988
  initialItems: ["carouselTrack"]
6938
6989
  });
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
6990
  var r$30 = t$60()({
6983
6991
  type: "container",
6984
6992
  attributeValues: {
6985
- subType: e$39()([
6993
+ subType: e$40()([
6986
6994
  "div",
6987
6995
  "header",
6988
6996
  "footer",
@@ -7006,7 +7014,7 @@ var r$30 = t$60()({
7006
7014
  "p",
7007
7015
  "span"
7008
7016
  ]),
7009
- live: e$39()([
7017
+ live: e$40()([
7010
7018
  "",
7011
7019
  "polite",
7012
7020
  "assertive"
@@ -7097,8 +7105,8 @@ var B$2 = {
7097
7105
  BlockHtml: t$59,
7098
7106
  BlockJsx: t$58,
7099
7107
  Button: r$33,
7100
- Carousel: t$43,
7101
- CarouselTrack: t$42,
7108
+ Carousel: t$42,
7109
+ CarouselTrack: t$43,
7102
7110
  Channel: n$33,
7103
7111
  Container: r$30,
7104
7112
  Custom: t$57,
@@ -7231,8 +7239,7 @@ var B$2 = {
7231
7239
  top: "0",
7232
7240
  position: "absolute",
7233
7241
  "z-index": "210",
7234
- "background-color": "black",
7235
- opacity: "0.5"
7242
+ "background-color": "rgb(0 0 0 / 50%)"
7236
7243
  } },
7237
7244
  rootContainer: { default: {
7238
7245
  display: "flex",
@@ -7303,8 +7310,8 @@ var B$2 = {
7303
7310
  List: t$60()({
7304
7311
  type: "list",
7305
7312
  attributeValues: {
7306
- subType: e$39()(["ul", "ol"]),
7307
- source: e$39()(["none", "controlled"])
7313
+ subType: e$40()(["ul", "ol"]),
7314
+ source: e$40()(["none", "controlled"])
7308
7315
  },
7309
7316
  sourceType: "list",
7310
7317
  content: {
@@ -7467,7 +7474,7 @@ var B$2 = {
7467
7474
  definition: {
7468
7475
  label: "Modal Container",
7469
7476
  type: "modalContainer",
7470
- description: "A modal overlay container opened and closed through interactions; use for dialogs over the page.",
7477
+ 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
7478
  items: [],
7472
7479
  bindings: {},
7473
7480
  loadStrategy: "lazy",
@@ -7519,8 +7526,7 @@ var B$2 = {
7519
7526
  top: "0",
7520
7527
  position: "absolute",
7521
7528
  "z-index": "210",
7522
- "background-color": "black",
7523
- opacity: "0.5"
7529
+ "background-color": "rgb(0 0 0 / 50%)"
7524
7530
  } },
7525
7531
  rootContainer: { default: {
7526
7532
  display: "flex",
@@ -7604,8 +7610,8 @@ var B$2 = {
7604
7610
  Pagination: t$60()({
7605
7611
  type: "pagination",
7606
7612
  attributeValues: {
7607
- mode: e$39()(["pages", "loadMore"]),
7608
- target: e$39()(["url", "interaction"])
7613
+ mode: e$40()(["pages", "loadMore"]),
7614
+ target: e$40()(["url", "interaction"])
7609
7615
  },
7610
7616
  triggers: { onPageChange: {
7611
7617
  action: "onPageChange",
@@ -7857,6 +7863,7 @@ var B$2 = {
7857
7863
  ThemeToggle: r$32,
7858
7864
  Video: t$44
7859
7865
  };
7866
+ Object.fromEntries(Object.values(B$2).flatMap((e) => "serverTemplates" in e ? [[e.type, e.serverTemplates]] : []));
7860
7867
  /** Every declaration, read through the part of its shape this catalog uses. */
7861
7868
  var declarations$1 = B$2;
7862
7869
  /** Keyed by the schema `type`, which is what a document and every catalog address a type by. */
@@ -7867,7 +7874,7 @@ var elementCatalog = Object.fromEntries(Object.values(declarations$1).map((decla
7867
7874
  }]));
7868
7875
  Object.keys(elementCatalog);
7869
7876
  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 ?? {})]]));
7877
+ Object.fromEntries(Object.values(declarations$1).map((declaration) => [declaration.type, [...Object.keys(e$41), ...Object.keys(declaration.triggers ?? {})]]));
7871
7878
  /** The triggers only some types fire, by action name — for a step builder that wants the title the builder shows. */
7872
7879
  var typeTriggerDefinitions = Object.fromEntries(Object.values(declarations$1).flatMap((declaration) => Object.entries(declaration.triggers ?? {})));
7873
7880
  Object.fromEntries(Object.values(declarations$1).flatMap((declaration) => Object.entries(declaration.callbacks ?? {})));
@@ -7968,6 +7975,11 @@ var AUTHORING_CODES = {
7968
7975
  means: "a page that sends visitors it is not for to a page the space does not have",
7969
7976
  fix: "a page's id or slug (`''` is the home page), or a full URL"
7970
7977
  },
7978
+ "page-template": {
7979
+ kind: "refused",
7980
+ 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",
7981
+ 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"
7982
+ },
7971
7983
  "folder-undeclared": {
7972
7984
  kind: "refused",
7973
7985
  means: "a page, a layout or a folder filed in a folder the space does not declare",
@@ -8143,6 +8155,11 @@ var AUTHORING_CODES = {
8143
8155
  means: "an ancestor condition keyed by something that is neither a class name nor `'>'` (the parent)",
8144
8156
  fix: "`[card.name]` for a `styles()` declaration, or `'>'` for the parent whatever it wears"
8145
8157
  },
8158
+ "focus-on-field-box": {
8159
+ kind: "warned",
8160
+ 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",
8161
+ fix: "`'focus-within'` on the same class, or the state on the `field` slot"
8162
+ },
8146
8163
  "heading-level-overridden": {
8147
8164
  kind: "warned",
8148
8165
  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",
@@ -8380,7 +8397,7 @@ var AUTHORING_CODES = {
8380
8397
  },
8381
8398
  "not-found-not-a-template": {
8382
8399
  kind: "refused",
8383
- means: "`notFound` that is not one `{{ expression }}` against the answer — never `true`, so never a 404",
8400
+ means: "`notFound`, a provider's or a page's, that is not one `{{ expression }}` — never `true`, so never a 404",
8384
8401
  fix: "`notFound: '{{ source.found == false }}'`"
8385
8402
  },
8386
8403
  "server-data-in-browser": {
@@ -8403,6 +8420,11 @@ var AUTHORING_CODES = {
8403
8420
  means: "a control in a form with no `name` — its value never reaches `values`",
8404
8421
  fix: "`formControl({ name: 'email', … })`"
8405
8422
  },
8423
+ "required-message-unused": {
8424
+ kind: "warned",
8425
+ 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",
8426
+ fix: "`required: true` on the field, or no message"
8427
+ },
8406
8428
  "form-control-name-taken": {
8407
8429
  kind: "warned",
8408
8430
  means: "two controls in one form with one name — one overwrites the other",
@@ -8983,7 +9005,7 @@ var sourceRow = (listSource, items) => {
8983
9005
  index: pathTo(`${listSource}.index`, 0)
8984
9006
  };
8985
9007
  };
8986
- var e$38 = [
9008
+ var e$39 = [
8987
9009
  "variables",
8988
9010
  "navigation",
8989
9011
  "auth",
@@ -8993,7 +9015,7 @@ var e$38 = [
8993
9015
  "flags",
8994
9016
  "computed"
8995
9017
  ];
8996
- e$38.filter((e) => e !== "state" && e !== "computed");
9018
+ e$39.filter((e) => e !== "state" && e !== "computed");
8997
9019
  var t$30 = {
8998
9020
  DISPLAY: "display",
8999
9021
  FLEX_WRAP: "flex-wrap",
@@ -9199,7 +9221,7 @@ Object.values(t$30).map((e) => ({
9199
9221
  label: e
9200
9222
  }));
9201
9223
  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];
9224
+ [...e$39];
9203
9225
  /** Both binding forms as one list. The map form targets attributes, which is what nearly every binding does. */
9204
9226
  var toBindingSpecs = (bind) => Array.isArray(bind) ? bind : Object.entries(bind).map(([to, source]) => ({
9205
9227
  to,
@@ -9511,20 +9533,20 @@ var tabContainerItem = defineElement(B$2.TabContainerItem);
9511
9533
  var text$1 = defineElement(B$2.Text);
9512
9534
  var themeToggle = defineElement(B$2.ThemeToggle);
9513
9535
  var video = defineElement(B$2.Video);
9514
- var e$32 = (e) => typeof e == "object" && !!e && !Array.isArray(e);
9536
+ var e$33 = (e) => typeof e == "object" && !!e && !Array.isArray(e);
9515
9537
  var STEP_TYPES = [
9516
9538
  "trigger",
9517
9539
  "globalCallback",
9518
9540
  "callback",
9519
9541
  "utility"
9520
9542
  ];
9521
- var e$30 = {
9543
+ var e$31 = {
9522
9544
  desktop: 1024,
9523
9545
  tablet: 768,
9524
9546
  mobile: 0
9525
9547
  };
9526
- var t$26 = (t) => e$30[t] === 0 ? "0" : `${String(e$30[t] / 16)}rem`;
9527
- var e$29 = [
9548
+ var t$26 = (t) => e$31[t] === 0 ? "0" : `${String(e$31[t] / 16)}rem`;
9549
+ var e$30 = [
9528
9550
  "desktop",
9529
9551
  "tablet",
9530
9552
  "mobile"
@@ -9532,14 +9554,14 @@ var e$29 = [
9532
9554
  Object.values(/* @__PURE__ */ function(e) {
9533
9555
  return e.COLOR = "color", e.SPACING = "spacing", e.SHADOW = "shadow", e.CUSTOM = "custom", e;
9534
9556
  }({}));
9535
- var e$27 = {
9557
+ var e$28 = {
9536
9558
  "motion-reduce": "@media (prefers-reduced-motion: reduce)",
9537
9559
  "motion-safe": "@media (prefers-reduced-motion: no-preference)"
9538
9560
  };
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}\)`;
9561
+ var t$23 = Object.keys(e$28);
9562
+ var r$18 = (t) => Object.hasOwn(e$28, t);
9563
+ var i$18 = String.raw`\d+(?:\.\d+)?(?:px|rem|em|ch)`;
9564
+ var a$15 = String.raw`\((?:min|max)-width:\s*${i$18}\)`;
9543
9565
  var o$17 = new RegExp(String.raw`^container(?:\s+(?<name>-?[_a-zA-Z][\w-]*))?\s+(?<query>${a$15}(?:\s+and\s+${a$15})?)$`);
9544
9566
  var s$15 = (e) => {
9545
9567
  let t = o$17.exec(e.trim());
@@ -9553,7 +9575,7 @@ var c$12 = (e) => {
9553
9575
  let t = s$15(e);
9554
9576
  if (t) return t.name ? `container ${t.name} ${t.query}` : `container ${t.query}`;
9555
9577
  };
9556
- var e$26 = [
9578
+ var e$27 = [
9557
9579
  "before",
9558
9580
  "after",
9559
9581
  "marker",
@@ -9572,7 +9594,7 @@ var t$22 = {
9572
9594
  selection: "::selection"
9573
9595
  };
9574
9596
  var n$19 = (e) => Object.hasOwn(t$22, e);
9575
- var i$16 = [
9597
+ var i$17 = [
9576
9598
  "before",
9577
9599
  "after",
9578
9600
  "marker"
@@ -9633,7 +9655,7 @@ var l$8 = {
9633
9655
  ...c$11
9634
9656
  ]
9635
9657
  };
9636
- var u$7 = (e, t) => {
9658
+ var u$8 = (e, t) => {
9637
9659
  let n = l$8[e];
9638
9660
  return !n || t.startsWith("--") || n.includes(t);
9639
9661
  };
@@ -9672,7 +9694,7 @@ var m$4 = (e) => {
9672
9694
  let t = p$8(e.trim());
9673
9695
  return t?.length ? t.every((e) => /^(["']).*\1$/s.test(e) || d$7.has(e.toLowerCase()) || f$5.test(e) && e.endsWith(")")) : !1;
9674
9696
  };
9675
- var e$25 = [
9697
+ var e$26 = [
9676
9698
  "hover",
9677
9699
  "focus",
9678
9700
  "focus-visible",
@@ -9707,7 +9729,7 @@ var r$16 = {
9707
9729
  disabled: 13,
9708
9730
  hidden: 14
9709
9731
  };
9710
- var i$15 = (e) => Object.hasOwn(r$16, e);
9732
+ var i$16 = (e) => Object.hasOwn(r$16, e);
9711
9733
  var l$7 = {
9712
9734
  current: ":is([aria-current]:not([aria-current=\"false\"]),[aria-pressed=\"true\"],[aria-selected=\"true\"],[role=\"tabpanel\"]:not([hidden]))",
9713
9735
  expanded: "[aria-expanded=\"true\"]",
@@ -9717,7 +9739,7 @@ var l$7 = {
9717
9739
  odd: ":nth-child(odd)",
9718
9740
  even: ":nth-child(even)"
9719
9741
  };
9720
- var u$6 = (e) => (i$15(e) ? l$7[e] : void 0) ?? `:${e}`;
9742
+ var u$7 = (e) => (i$16(e) ? l$7[e] : void 0) ?? `:${e}`;
9721
9743
  var p$7 = (e) => e === ">";
9722
9744
  var n$16 = (e) => {
9723
9745
  let t = typeof e == "string" && e.trim() !== "" ? Number(e) : e;
@@ -9753,7 +9775,7 @@ var r$13 = {
9753
9775
  del: "delete",
9754
9776
  return: "enter"
9755
9777
  };
9756
- var i$12 = /* @__PURE__ */ new Set([
9778
+ var i$13 = /* @__PURE__ */ new Set([
9757
9779
  "escape",
9758
9780
  "enter",
9759
9781
  "tab",
@@ -9786,7 +9808,7 @@ var o$11 = (e, t) => {
9786
9808
  }
9787
9809
  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
9810
  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) };
9811
+ 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
9812
  };
9791
9813
  var s$9 = (e, t = !1) => {
9792
9814
  let n = e.split(",").map((e) => o$11(e, t));
@@ -9839,7 +9861,7 @@ var steps_exports$1 = /* @__PURE__ */ __exportAll({
9839
9861
  * the builders useless for exactly the flows that matter most.
9840
9862
  */
9841
9863
  var builtinTrigger = (trigger) => {
9842
- if (Object.hasOwn(e$40, trigger)) return e$40[trigger];
9864
+ if (Object.hasOwn(e$41, trigger)) return e$41[trigger];
9843
9865
  return Object.hasOwn(typeTriggerDefinitions, trigger) ? typeTriggerDefinitions[trigger] : void 0;
9844
9866
  };
9845
9867
  var on = (trigger, params = {}) => {
@@ -10150,7 +10172,7 @@ var carouselGoTo = (target, index) => carouselStep(target, "goTo", "Go To Slide"
10150
10172
  var carouselPlay = (target) => carouselStep(target, "play", "Play");
10151
10173
  /** The carousel `target` stops moving by itself until it is told to play. */
10152
10174
  var carouselPause = (target) => carouselStep(target, "pause", "Pause");
10153
- var e$21 = {
10175
+ var e$22 = {
10154
10176
  origin: "Requests to the same site",
10155
10177
  all: "All cached requests",
10156
10178
  elements: "Only these containers",
@@ -10162,7 +10184,7 @@ var t$16 = (t, n = () => !0) => ({
10162
10184
  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
10185
  default: t[0],
10164
10186
  options: [...t],
10165
- optionLabels: Object.fromEntries(t.map((t) => [t, e$21[t]])),
10187
+ optionLabels: Object.fromEntries(t.map((t) => [t, e$22[t]])),
10166
10188
  label: "Refresh cached requests",
10167
10189
  canBind: !1,
10168
10190
  when: n
@@ -10242,7 +10264,7 @@ var t$15 = {
10242
10264
  preview: { cancelled: "" }
10243
10265
  }
10244
10266
  };
10245
- var e$20 = {
10267
+ var e$21 = {
10246
10268
  login: {
10247
10269
  source: "auth",
10248
10270
  title: "Auth Login",
@@ -10328,7 +10350,7 @@ var e$20 = {
10328
10350
  params: {}
10329
10351
  }
10330
10352
  };
10331
- var e$19 = { hostAction: {
10353
+ var e$20 = { hostAction: {
10332
10354
  source: "host",
10333
10355
  title: "Host Action",
10334
10356
  strictParams: !0,
@@ -10345,7 +10367,7 @@ var e$19 = { hostAction: {
10345
10367
  }
10346
10368
  }
10347
10369
  } };
10348
- var e$18 = { navigate: {
10370
+ var e$19 = { navigate: {
10349
10371
  source: "navigation",
10350
10372
  title: "Navigate",
10351
10373
  strictParams: !0,
@@ -10372,7 +10394,7 @@ var e$18 = { navigate: {
10372
10394
  }
10373
10395
  }
10374
10396
  } };
10375
- var e$17 = { invalidateQueries: {
10397
+ var e$18 = { invalidateQueries: {
10376
10398
  source: "queries",
10377
10399
  title: "Invalidate Queries",
10378
10400
  strictParams: !0,
@@ -10605,11 +10627,11 @@ var BUILTIN_GLOBAL_CALLBACKS = {
10605
10627
  default: ""
10606
10628
  } }
10607
10629
  },
10608
- ...e$19,
10609
- ...e$18,
10610
10630
  ...e$20,
10631
+ ...e$19,
10632
+ ...e$21,
10611
10633
  ...t$15,
10612
- ...e$17
10634
+ ...e$18
10613
10635
  };
10614
10636
  var steps_exports = /* @__PURE__ */ __exportAll({
10615
10637
  SET_STATE_TYPES: () => SET_STATE_TYPES,
@@ -10848,10 +10870,11 @@ var t$14 = (e) => {
10848
10870
  var t$13 = "[A-Za-z_][A-Za-z0-9_]*";
10849
10871
  RegExp(`^${t$13}$`);
10850
10872
  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");
10873
+ var e$9 = "/data/";
10874
+ var e$7 = (e) => typeof e == "string" && /\{\{[\s\S]*?\}\}|\{%[\s\S]*?%\}/.test(e);
10875
+ var i$5 = (e) => `value|round(0, '${e}')`;
10876
+ i$5("floor"), i$5("ceil");
10877
+ new Set(Object.entries(g$4.content.defaultStyle.subTypes).filter(([, variant]) => "field" in variant.style).map(([subType]) => subType));
10855
10878
  /** A container with no tag of its own named: the row `list-row-not-li` makes an `<li>` without asking. */
10856
10879
  var isPlainContainer = (element) => element.definition.type === "container" && (element.attributes.subType === void 0 || element.attributes.subType === "div");
10857
10880
  new Set(STEP_TYPES);
@@ -11010,7 +11033,7 @@ Array.from({ length: 9 }, (e, t) => ({
11010
11033
  Object.entries(AUTHORING_CODES).filter(([, entry]) => entry.kind === "suggested").map(([code]) => code);
11011
11034
  var URL_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
11012
11035
  /** 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);
11036
+ var isUrlTarget = (target) => typeof target === "string" && !e$7(target) && URL_SCHEME.test(target);
11014
11037
  var isRoot = (element) => !element.definition.parentId;
11015
11038
  var stepsOf$1 = (element) => Object.values(element.definition.interactions ?? {});
11016
11039
  /**
@@ -11021,7 +11044,7 @@ var settleUnknownKeys = (record, known, what, on, report, defaults = {}) => {
11021
11044
  for (const key of Object.keys(record)) {
11022
11045
  if (known.includes(key)) continue;
11023
11046
  const wrapped = record[key];
11024
- if (e$32(wrapped) && Object.keys(wrapped).length > 0 && Object.keys(wrapped).every((inner) => known.includes(inner))) {
11047
+ if (e$33(wrapped) && Object.keys(wrapped).length > 0 && Object.keys(wrapped).every((inner) => known.includes(inner))) {
11025
11048
  const inners = Object.keys(wrapped);
11026
11049
  if (inners.every((inner) => record[inner] === void 0 || record[inner] === defaults[inner])) {
11027
11050
  Object.assign(record, wrapped);
@@ -11302,7 +11325,7 @@ t$4.map((e) => {
11302
11325
  fieldOf: (e) => t.get(e)
11303
11326
  };
11304
11327
  });
11305
- Object.fromEntries(e$29.map((e) => [e, t$26(e)]));
11328
+ Object.fromEntries(e$30.map((e) => [e, t$26(e)]));
11306
11329
  /** Every kebab-case CSS property Plitzi's style engine understands. Values written to a definition must use these
11307
11330
  * exact keys — camelCase or unknown keys are rejected. */
11308
11331
  var cssProperties = Array.from(new Set(Object.values(t$30))).sort();
@@ -12087,7 +12110,7 @@ var byProperty = (rules) => {
12087
12110
  return responsive;
12088
12111
  };
12089
12112
  /** 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;
12113
+ var STYLE_STATES = e$26;
12091
12114
  var STYLE_STATE_SET = new Set(STYLE_STATES);
12092
12115
  var RULE_SET_KEYS = /* @__PURE__ */ new Set([
12093
12116
  "css",
@@ -12151,11 +12174,11 @@ var assertPseudo = (pseudo, part) => {
12151
12174
  const ruleSets = partRuleSets(part);
12152
12175
  for (const rules of ruleSets) for (const [property, value] of Object.entries(rules)) {
12153
12176
  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.`);
12177
+ 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
12178
  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
12179
  continue;
12157
12180
  }
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.`);
12181
+ 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
12182
  }
12160
12183
  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
12184
  };
@@ -12163,7 +12186,7 @@ var toPseudos = (pseudos) => {
12163
12186
  const parts = /* @__PURE__ */ new Map();
12164
12187
  for (const [name, spec] of Object.entries(pseudos ?? {})) {
12165
12188
  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(", ")}.`);
12189
+ 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
12190
  const part = partOf(spec);
12168
12191
  const rules = {
12169
12192
  base: toResponsive(part.css),
@@ -13789,7 +13812,7 @@ var createTw = ({ colors = {} } = {}) => (classes) => {
13789
13812
  return rules;
13790
13813
  };
13791
13814
  createTw();
13792
- [...new Set(STYLE_STATES.filter((state) => state !== "hidden"))].filter((state) => u$6(state) !== `:${state}`);
13815
+ [...new Set(STYLE_STATES.filter((state) => state !== "hidden"))].filter((state) => u$7(state) !== `:${state}`);
13793
13816
  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
13817
  new Set(STYLE_STATES);
13795
13818
  new Set([
@@ -14972,7 +14995,7 @@ var V$1 = (e) => {
14972
14995
  for (let n of e.filter((e) => x$2.test(e))) t.set(M$1(n), [...t.get(M$1(n)) ?? [], n]);
14973
14996
  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
14997
  };
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" ? [{
14998
+ 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
14999
  src: t.src,
14977
15000
  type: t.type,
14978
15001
  isMain: t.isMain === !0
@@ -14986,7 +15009,7 @@ var U$1 = (t, n) => {
14986
15009
  } catch (e) {
14987
15010
  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
15011
  }
14989
- if (!e$32(o)) return { problem: y$1("vendor-manifest-invalid", i, `${i} is not a manifest, so ${n} does not run.`, a) };
15012
+ if (!e$33(o)) return { problem: y$1("vendor-manifest-invalid", i, `${i} is not a manifest, so ${n} does not run.`, a) };
14990
15013
  let s = H$1(o), l = s.find((e) => e.type === "script" && e.isMain) ?? s.find((e) => e.type === "script");
14991
15014
  if (!l) return { problem: y$1("vendor-script-missing", i, `${i} names no script to run, so ${n} does not run.`, a) };
14992
15015
  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 +15133,7 @@ var ce$1 = (e, t) => {
15110
15133
  var Z$1 = (t) => {
15111
15134
  try {
15112
15135
  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);
15136
+ return e$33(n) && [n.dependencies, n.devDependencies].some((t) => e$33(t) && "@plitzi/sdk-server" in t);
15114
15137
  } catch {
15115
15138
  return !1;
15116
15139
  }
@@ -15233,7 +15256,7 @@ var publicData = (folder) => fileReader(folder, "/");
15233
15256
  * never serves (`dataDir`) — for `authorSpace`'s `serverData`: bindings are held to the file, and a provider reading
15234
15257
  * one from the browser is refused.
15235
15258
  */
15236
- var projectData = (folder) => fileReader(folder, e$8);
15259
+ var projectData = (folder) => fileReader(folder, e$9);
15237
15260
  /**
15238
15261
  * The declaration of every plugin under `folder` — `src/plugins/<Name>/declaration.ts`, its default export — for
15239
15262
  * `authorSpace`'s `plugins`: what each fires, answers and reads. Found by folder, as the server finds the plugins
@@ -15255,7 +15278,7 @@ var pluginDeclarations = async (folder) => {
15255
15278
  var manifestOf$1 = (file) => {
15256
15279
  try {
15257
15280
  const manifest = JSON.parse(readFileSync(file, "utf-8"));
15258
- return e$32(manifest) ? manifest : {};
15281
+ return e$33(manifest) ? manifest : {};
15259
15282
  } catch {
15260
15283
  return {};
15261
15284
  }
@@ -15266,7 +15289,7 @@ var manifestOf$1 = (file) => {
15266
15289
  */
15267
15290
  var builtPluginTypes = (folder) => existsSync(folder) ? readdirSync(folder, { withFileTypes: true }).filter((entry) => entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name)).flatMap((entry) => {
15268
15291
  const { pluginSchema } = manifestOf$1(path.join(folder, entry.name, d$1));
15269
- return [entry.name, ...e$32(pluginSchema) ? Object.keys(pluginSchema) : []];
15292
+ return [entry.name, ...e$33(pluginSchema) ? Object.keys(pluginSchema) : []];
15270
15293
  }) : [];
15271
15294
  /**
15272
15295
  * The stylesheets of the plugins under `folder` — every `.css` in a plugin's folder, at any depth — as their text, for
@@ -15335,7 +15358,7 @@ var importProjectModule = async (file, root) => {
15335
15358
  * What the module exports as `space`, taken as a declaration when it has the shape of one. `authorSpace` checks the
15336
15359
  * rest of it, field by field, and says what is wrong — so this only has to tell a space from anything else.
15337
15360
  */
15338
- var isSpaceSpec = (value) => e$32(value) && typeof value.name === "string" && typeof value.permanentUrl === "string" && Array.isArray(value.pages);
15361
+ var isSpaceSpec = (value) => e$33(value) && typeof value.name === "string" && typeof value.permanentUrl === "string" && Array.isArray(value.pages);
15339
15362
  /**
15340
15363
  * `projectSpace` of the project at `root`, held to the same checks: for a tool that works on a project from another
15341
15364
  * folder — the CLI, run anywhere inside one. A project's own code calls `projectSpace()` or `authorProjectSpace()`.
@@ -15343,7 +15366,7 @@ var isSpaceSpec = (value) => e$32(value) && typeof value.name === "string" && ty
15343
15366
  var projectSpaceAt = async (root) => {
15344
15367
  const authoring = await projectAuthoringAt(root);
15345
15368
  const module = await importProjectModule(l$2(root, a$1, process.argv[1]), root);
15346
- const space = e$32(module) ? module.space : void 0;
15369
+ const space = e$33(module) ? module.space : void 0;
15347
15370
  if (!isSpaceSpec(space)) throw new ProjectSpaceError(a$1);
15348
15371
  return {
15349
15372
  space,
@@ -16294,7 +16317,8 @@ var unifiedDiff = (file, before, after, context = 2) => {
16294
16317
  * is left to the author, said with the command that would take the registry's.
16295
16318
  * - `skills`: `.claude/skills/plitzi-*` from the packages installed, each replaced whole.
16296
16319
  * - `renames`: a name a version renamed, still written in the source (`canTemplate`), at its file and line — and with
16297
- * `--write`, renamed.
16320
+ * `--write`, renamed. With it, what a version changed that the source may count on — a `formControl` written
16321
+ * before fields became optional — at its file and line, for a project last upgraded before: said, never written.
16298
16322
  *
16299
16323
  * Shown by default; `--write` makes it. What the space is — its pages, plugins, data — is the project's, never named.
16300
16324
  *
@@ -16355,6 +16379,42 @@ var renamesIn = (file, text) => RENAMES.filter((rename) => !rename.from || impor
16355
16379
  since: rename.since,
16356
16380
  what: rename.what
16357
16381
  }] : []));
16382
+ /** Before this version a `formControl` was required unless it said `required: false`; from it, optional unless `true`. */
16383
+ var OPTIONAL_FIELDS_SINCE = "0.38.9";
16384
+ /** The text of the call whose `(` is at `open`, up to its `)` — strings skipped, so a `)` inside one closes nothing. */
16385
+ var callArguments = (text, open) => {
16386
+ let depth = 0;
16387
+ let quote;
16388
+ for (let at = open; at < text.length; at++) {
16389
+ const char = text[at];
16390
+ if (quote && char === "\\") {
16391
+ at++;
16392
+ continue;
16393
+ }
16394
+ if (quote) {
16395
+ quote = char === quote ? void 0 : quote;
16396
+ continue;
16397
+ }
16398
+ if (/['"`]/.test(char)) quote = char;
16399
+ else if (char === "(") depth++;
16400
+ else if (char === ")" && --depth === 0) return text.slice(open + 1, at);
16401
+ }
16402
+ return text.slice(open + 1);
16403
+ };
16404
+ /**
16405
+ * A `formControl(…)` that writes no `required`: one that, written before fields became optional, was required, and now
16406
+ * takes an empty answer. Said, never written — whether a field must be answered is the space's to say. Found as written
16407
+ * in the call: one built by a helper of the project's own is the helper's to look at.
16408
+ */
16409
+ var optionalFieldsIn = (file, text) => [...text.matchAll(/\bformControl\(/g)].flatMap((match) => {
16410
+ const open = match.index + match[0].length - 1;
16411
+ return /\brequired\s*:/.test(callArguments(text, open)) ? [] : [{
16412
+ file,
16413
+ line: text.slice(0, match.index).split("\n").length,
16414
+ since: OPTIONAL_FIELDS_SINCE,
16415
+ what: "a formControl with no `required` is optional now — write `required: true` if it must be answered"
16416
+ }];
16417
+ });
16358
16418
  /** The lowest version a range allows, as numbers: `^0.38.2` → [0, 38, 2]. */
16359
16419
  var floorOf$1 = (range) => {
16360
16420
  const match = /(\d+)\.(\d+)\.(\d+)/.exec(range);
@@ -16451,8 +16511,20 @@ var readText$1 = async (file) => {
16451
16511
  return;
16452
16512
  }
16453
16513
  };
16514
+ var holdsFiles = async (folder) => {
16515
+ try {
16516
+ return (await fs.readdir(folder)).length > 0;
16517
+ } catch {
16518
+ return false;
16519
+ }
16520
+ };
16454
16521
  var planFiles = async (root, files, recorded, take) => Promise.all(Object.entries(files).map(async ([file, ours]) => {
16455
16522
  const yours = await readText$1(path.join(root, file));
16523
+ if (yours === void 0 && path.basename(file) === ".gitkeep" && await holdsFiles(path.join(root, file, ".."))) return {
16524
+ file,
16525
+ status: "current",
16526
+ ours
16527
+ };
16456
16528
  if (yours === void 0) return {
16457
16529
  file,
16458
16530
  status: "added",
@@ -16611,6 +16683,10 @@ var reportText$3 = (report, install) => {
16611
16683
  lines.push(report.renames.length === 0 ? chalk.green("renames: none written") : "renames:");
16612
16684
  lines.push(...report.renames.map((found) => chalk.yellow(` ${report.write ? "~" : "!"} ${found.file}:${String(found.line)} ${found.name} → ${found.to} (${found.since}: ${found.what})`)));
16613
16685
  }
16686
+ if (report.changes && report.changes.length > 0) {
16687
+ lines.push("changes the source may count on (look, then decide — never written):");
16688
+ lines.push(...report.changes.map((found) => chalk.yellow(` ! ${found.file}:${String(found.line)} (${found.since}) ${found.what}`)));
16689
+ }
16614
16690
  return lines.join("\n");
16615
16691
  };
16616
16692
  var upgrade = async (parts, options) => {
@@ -16707,8 +16783,11 @@ var upgrade = async (parts, options) => {
16707
16783
  }
16708
16784
  if (wanted.has("renames")) {
16709
16785
  const found = [];
16786
+ const changes = [];
16787
+ const before = record === void 0 || isBelow(record.cli, OPTIONAL_FIELDS_SINCE);
16710
16788
  for (const file of await sourceFiles(root)) {
16711
16789
  const text = await readText$1(path.join(root, file));
16790
+ if (before && text !== void 0) changes.push(...optionalFieldsIn(file, text));
16712
16791
  const inFile = text === void 0 ? [] : renamesIn(file, text);
16713
16792
  if (write && text !== void 0 && inFile.length > 0) {
16714
16793
  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 +16796,7 @@ var upgrade = async (parts, options) => {
16717
16796
  found.push(...inFile);
16718
16797
  }
16719
16798
  report.renames = found;
16799
+ report.changes = changes;
16720
16800
  }
16721
16801
  console.log(options.json ? JSON.stringify(report) : reportText$3(report, installCommand(manager)));
16722
16802
  if (report.packages?.install === "failed") {
@@ -18389,15 +18469,32 @@ var checkAccount = async (root, username) => {
18389
18469
  password
18390
18470
  };
18391
18471
  };
18472
+ /** Signs the page's browser in as the account, through the server's `/auth` routes: what went wrong, or nothing. */
18473
+ var signInAs = async (page, origin, account) => {
18474
+ const answer = await page.request.post(`${origin}${SIGN_IN_PATH}`, { data: {
18475
+ username: account.username,
18476
+ password: account.password
18477
+ } });
18478
+ if (answer.ok()) return;
18479
+ 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`;
18480
+ };
18481
+ var withoutTrailingSlash = (path) => path.length > 1 ? path.replace(/\/+$/, "") : path;
18482
+ /** Where the page landed, when that is not the path it was opened at — sent to the sign-in, most often. */
18483
+ var sentElsewhere = (page, pathname) => {
18484
+ const landed = new URL(page.url()).pathname;
18485
+ const asked = new URL(pathname, page.url()).pathname;
18486
+ return withoutTrailingSlash(landed) === withoutTrailingSlash(asked) ? void 0 : landed;
18487
+ };
18392
18488
  /** The space the project declares, authored. Only for one written here. */
18393
18489
  var projectSpace = async (root) => {
18394
18490
  const project = await loadProjectSpace(root);
18395
18491
  if ("problem" in project) return { problem: `${project.problem} There is nothing to check the page against.` };
18396
18492
  try {
18397
- const { handles, schema } = authorSpace(project.space, project.authoring);
18493
+ const { handles, schema, style } = authorSpace(project.space, project.authoring);
18398
18494
  return {
18399
18495
  handles,
18400
- schema
18496
+ schema,
18497
+ schemes: SCHEMES.filter((scheme) => style.theme.schemes.includes(scheme))
18401
18498
  };
18402
18499
  } catch (error) {
18403
18500
  return { problem: `The space does not author — npm run author says why: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}` };
@@ -18813,17 +18910,12 @@ var checkAt = async (browser, origin, pathname, width, scheme, authored, asked,
18813
18910
  devTools: false,
18814
18911
  lists: {}
18815
18912
  });
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
- }
18913
+ const notSignedIn = account && await signInAs(page, origin, account);
18914
+ if (notSignedIn) return unchecked({
18915
+ code: "not-signed-in",
18916
+ message: notSignedIn,
18917
+ width
18918
+ });
18827
18919
  const consoleErrors = [];
18828
18920
  const failedRequests = [];
18829
18921
  page.on("pageerror", (error) => consoleErrors.push(error.message));
@@ -18857,8 +18949,8 @@ var checkAt = async (browser, origin, pathname, width, scheme, authored, asked,
18857
18949
  consoleErrors,
18858
18950
  failedRequests
18859
18951
  };
18860
- const landed = new URL(page.url()).pathname;
18861
- if (withoutTrailingSlash(landed) !== withoutTrailingSlash(new URL(`${origin}${pathname}`).pathname)) return {
18952
+ const landed = sentElsewhere(page, pathname);
18953
+ if (landed) return {
18862
18954
  ...unchecked({
18863
18955
  code: "redirected",
18864
18956
  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 +19078,7 @@ var clickedText = (clicked) => {
18986
19078
  ];
18987
19079
  };
18988
19080
  var reportText$2 = (report, asked) => {
18989
- const head = `${report.path} at ${String(report.width)} px`;
19081
+ const head = `${report.path} at ${String(report.width)} px${report.scheme ? `, ${report.scheme}` : ""}`;
18990
19082
  const held = [
18991
19083
  ...report.notFound ? [" · sent with status 404: the page for an address that shows nothing"] : [],
18992
19084
  ...listsText(report.lists),
@@ -19008,7 +19100,6 @@ var reportText$2 = (report, asked) => {
19008
19100
  ...held
19009
19101
  ].join("\n");
19010
19102
  };
19011
- var withoutTrailingSlash = (path) => path.length > 1 ? path.replace(/\/+$/, "") : path;
19012
19103
  var check = async (routes, options) => {
19013
19104
  const project = await projectHere("whose page to check");
19014
19105
  if (!project) return;
@@ -19062,17 +19153,26 @@ var checkRoutes = async (project, routes, options) => {
19062
19153
  if ("problem" in browser) return browser;
19063
19154
  try {
19064
19155
  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));
19156
+ for (const route of routes) for (const width of options.widths) {
19157
+ const report = await checkAt(browser, where.origin, route, width, options.scheme, authored, options.asked, options.ssr, options.account, options.click);
19158
+ reports.push({
19159
+ ...report,
19160
+ ...options.scheme ? { scheme: options.scheme } : {}
19161
+ });
19162
+ }
19066
19163
  return reports;
19067
19164
  } finally {
19068
19165
  await browser.close();
19069
19166
  }
19070
19167
  };
19071
- /** The paths of the space's pages that take no parameter — the ones a check can open as they are. */
19168
+ /** What a whole-project check opens: every page with no parameter in its path, in every theme the space can show. */
19072
19169
  var staticPaths = async (root) => {
19073
19170
  const authored = await projectSpace(root);
19074
19171
  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)));
19172
+ return {
19173
+ 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))),
19174
+ schemes: authored.schemes
19175
+ };
19076
19176
  };
19077
19177
  //#endregion
19078
19178
  //#region src/commands/functions.ts
@@ -21186,12 +21286,12 @@ var measure = async (browser, url, width, scheme, read = []) => {
21186
21286
  screen: await page.screenshot({ fullPage: true })
21187
21287
  };
21188
21288
  };
21189
- var count$2 = (amount, one, many = `${one}s`) => `${String(amount)} ${amount === 1 ? one : many}`;
21289
+ var count$3 = (amount, one, many = `${one}s`) => `${String(amount)} ${amount === 1 ? one : many}`;
21190
21290
  var summaryText = (summary, out, stale) => [
21191
21291
  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(", ")}`] : [],
21292
+ ` 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(", ")}` : ""}`,
21293
+ ` outline ${count$3(summary.blocks, "block")} at ${summary.widths.join(", ")} px`,
21294
+ ...summary.lists.length > 0 ? [` lists ${summary.lists.map((list) => `${list.file} (${count$3(list.rows, "row")})`).join(", ")}`] : [],
21195
21295
  ` pictures ${String(summary.pictures)} in assets.json`,
21196
21296
  ...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
21297
  ` next ${path.join(out, "IMPORT.md")} says what was not carried over`
@@ -22409,7 +22509,7 @@ var projectConnectors = async (root) => {
22409
22509
  manifest: value.manifest
22410
22510
  }] : []);
22411
22511
  };
22412
- var count$1 = (entries, noun) => entries ? `${String(entries.length)} ${noun}${entries.length === 1 ? "" : "s"}` : `the space’s ${noun}s as they are`;
22512
+ var count$2 = (entries, noun) => entries ? `${String(entries.length)} ${noun}${entries.length === 1 ? "" : "s"}` : `the space’s ${noun}s as they are`;
22413
22513
  /**
22414
22514
  * 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
22515
  * renders empty there:
@@ -22494,7 +22594,7 @@ var pushSpaceOf = async (root, connection, space, { base, force, downloads }) =>
22494
22594
  outcome: "unchanged",
22495
22595
  draft: data.draft
22496
22596
  };
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."));
22597
+ 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
22598
  return {
22499
22599
  outcome: "pushed",
22500
22600
  draft: data.draft
@@ -23522,7 +23622,107 @@ var _ = `[data-motion-enter], [data-motion-stagger] > *, [data-motion-loop] { an
23522
23622
  "[data-motion-on=\"scroll\"][data-motion-enter], [data-motion-on=\"scroll\"][data-motion-stagger] > * { animation-timeline: auto, auto !important; }"
23523
23623
  ].join("\n");
23524
23624
  //#endregion
23625
+ //#region src/commands/shotSteps.ts
23626
+ var STEP_NAMES = [
23627
+ "click",
23628
+ "type",
23629
+ "press",
23630
+ "wait",
23631
+ "wait-for",
23632
+ "shot",
23633
+ "frames"
23634
+ ];
23635
+ /** The longest a script may wait in one step: a page that needs more is waiting for something else. */
23636
+ var MAX_WAIT_MS = 3e4;
23637
+ var unquoted = (text) => text.replace(/^(["'])(.*)\1$/s, "$2");
23638
+ var count$1 = (value, at, max) => {
23639
+ const parsed = Number(value);
23640
+ return Number.isInteger(parsed) && parsed > 0 && parsed <= max ? parsed : { problem: `${at}: "${value ?? ""}" is not a whole number from 1 to ${String(max)}` };
23641
+ };
23642
+ /** A script, read into its steps — or what is wrong with it, said by its step. */
23643
+ var parseSteps = (script, every) => {
23644
+ const lines = script.split(/[;\n]/).map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("#"));
23645
+ if (lines.length === 0) return { problem: "--steps has no step: click, type, press, wait, wait-for, shot or frames, separated by \";\"" };
23646
+ const steps = [];
23647
+ for (const [index, line] of lines.entries()) {
23648
+ const at = `step ${String(index + 1)} ("${line}")`;
23649
+ const [name, ...rest] = line.split(/\s+/);
23650
+ const argument = unquoted(rest.join(" "));
23651
+ switch (name) {
23652
+ case "click":
23653
+ case "wait-for":
23654
+ if (!argument) return { problem: `${at}: ${name} needs an element — its name or a CSS selector` };
23655
+ steps.push({
23656
+ kind: name,
23657
+ target: argument
23658
+ });
23659
+ break;
23660
+ case "type": {
23661
+ const [target, ...words] = rest;
23662
+ if (!target || words.length === 0) return { problem: `${at}: type needs an element and the text — type <element> <text>` };
23663
+ steps.push({
23664
+ kind: "type",
23665
+ target,
23666
+ text: unquoted(words.join(" "))
23667
+ });
23668
+ break;
23669
+ }
23670
+ case "press":
23671
+ if (!argument) return { problem: `${at}: press needs a key — Enter, Escape, ArrowDown…` };
23672
+ steps.push({
23673
+ kind: "press",
23674
+ key: argument
23675
+ });
23676
+ break;
23677
+ case "wait": {
23678
+ const ms = count$1(rest[0], at, MAX_WAIT_MS);
23679
+ if (typeof ms !== "number") return ms;
23680
+ steps.push({
23681
+ kind: "wait",
23682
+ ms
23683
+ });
23684
+ break;
23685
+ }
23686
+ case "shot":
23687
+ steps.push({
23688
+ kind: "shot",
23689
+ ...argument ? { label: argument } : {}
23690
+ });
23691
+ break;
23692
+ case "frames": {
23693
+ const frames = count$1(rest[0], at, 60);
23694
+ if (typeof frames !== "number") return frames;
23695
+ const apart = rest.length < 2 ? every : count$1(rest[1], at, MAX_WAIT_MS);
23696
+ if (typeof apart !== "number") return apart;
23697
+ steps.push({
23698
+ kind: "frames",
23699
+ count: frames,
23700
+ every: apart
23701
+ });
23702
+ break;
23703
+ }
23704
+ default: return { problem: `${at}: "${name}" is not a step — the steps are ${STEP_NAMES.join(", ")}` };
23705
+ }
23706
+ }
23707
+ return steps;
23708
+ };
23709
+ //#endregion
23525
23710
  //#region src/commands/shot.ts
23711
+ /**
23712
+ * `plitzi page shot`: a picture of one page of the project — and, asked, the numbers that say what a picture would.
23713
+ *
23714
+ * plitzi page shot /about --width 390 --scheme dark
23715
+ * plitzi page shot / --compare https://example.com --width 1440 # beside another site: what differs, and how
23716
+ * plitzi page shot / --frames 4 --every 500 # what moves: a marquee, an autoplay
23717
+ * plitzi page shot / --clip pricing-table # one element, scrolled to wherever it is
23718
+ * plitzi page shot / --scroll-to faq --viewport --frames 6 # the screen at that section, as it animates
23719
+ *
23720
+ * The project's server has to be running (`npm start`). `--json` answers in one object, for a tool or an agent. The
23721
+ * dev tools' badge is never in the picture; a picture of the whole page has every lazy image loaded and shows every
23722
+ * arrival that waits for the scroll as it ends — taken without scrolling, they would be holes where the sections are.
23723
+ */
23724
+ /** When a run's first picture is taken: once the page settles, or as soon as its HTML is in. */
23725
+ var SHOT_MOMENTS = ["settled", "load"];
23526
23726
  /** A region moves when more than this share of it changed between two frames: below is anti-aliasing and carets. */
23527
23727
  var MOVING_PERCENT = .5;
23528
23728
  /** How long a whole page waits for its pictures before it is taken anyway. */
@@ -23533,8 +23733,8 @@ var TEXTS_SHOWN = 12;
23533
23733
  var DRIFT_PX = 2;
23534
23734
  /** An arrival that waits for the scroll and has not had it: one waiting to be seen, or one tied to the scroll. */
23535
23735
  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}"]`;
23736
+ /** A selector — CSS, or Playwright's own (`text=Saved`, `role=button[name="Publish"]`) — or an element's name. */
23737
+ var selectorOf = (target) => /^([#.[]|(text|role|css|xpath)=)/.test(target) ? target : `[data-plitzi-el="${target}"]`;
23538
23738
  var framing = (view) => {
23539
23739
  if (view.clip) return {
23540
23740
  kind: "element",
@@ -23559,7 +23759,10 @@ var pictureOf = async (browser, url, view, origin) => {
23559
23759
  colorScheme: view.scheme ?? "light",
23560
23760
  reducedMotion: view.reducedMotion ? "reduce" : "no-preference"
23561
23761
  });
23562
- if (!await openPage(page, url)) return { problem: `Nothing answers at ${url}.` };
23762
+ const notSignedIn = origin && view.account && await signInAs(page, origin, view.account);
23763
+ if (notSignedIn) return { problem: `--as ${notSignedIn}.` };
23764
+ 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" : ""}` };
23765
+ const sentTo = origin ? sentElsewhere(page, new URL(url).pathname) : void 0;
23563
23766
  if (view.waitFor) {
23564
23767
  if (!await page.waitForSelector(selectorOf(view.waitFor), { timeout: 1e4 }).catch(() => null)) return { problem: `${view.waitFor} did not appear on ${url} within 10 s.` };
23565
23768
  }
@@ -23576,7 +23779,8 @@ var pictureOf = async (browser, url, view, origin) => {
23576
23779
  png: await capture(page, framing(view)),
23577
23780
  scheme: await paintedScheme(page, view.scheme ?? "light"),
23578
23781
  settled,
23579
- unloaded: pictures?.missing ?? 0
23782
+ unloaded: pictures?.missing ?? 0,
23783
+ ...sentTo ? { sentTo } : {}
23580
23784
  };
23581
23785
  };
23582
23786
  var shiftText = (shift) => shift !== void 0 && Math.abs(shift) > DRIFT_PX ? ` (${shift > 0 ? "+" : ""}${String(shift)}px there)` : "";
@@ -23716,6 +23920,65 @@ var clickThrough = async (taken, how, clicks, { frames, every }) => {
23716
23920
  clicked
23717
23921
  };
23718
23922
  };
23923
+ /** What a step is, as its label says it. */
23924
+ var stepText = (step) => {
23925
+ switch (step.kind) {
23926
+ case "click":
23927
+ case "wait-for": return `${step.kind} ${step.target}`;
23928
+ case "type": return `type ${step.target}`;
23929
+ case "press": return `press ${step.key}`;
23930
+ case "wait": return `wait ${String(step.ms)} ms`;
23931
+ case "shot": return step.label ?? "shot";
23932
+ case "frames": return `frames ${String(step.count)}`;
23933
+ }
23934
+ };
23935
+ /**
23936
+ * A `--steps` script played on the page: what each step does, and every picture the script asks for — each labelled with
23937
+ * its step and the time since the first, and measured against the one before it, so "nothing changed" is said.
23938
+ */
23939
+ var playSteps = async (taken, how, steps) => {
23940
+ const { page } = taken;
23941
+ const started = Date.now();
23942
+ const moments = [{
23943
+ label: "before",
23944
+ png: taken.png
23945
+ }];
23946
+ const changes = [];
23947
+ const picture = async (label) => {
23948
+ const before = moments[moments.length - 1].png;
23949
+ const png = await capture(page, how);
23950
+ const at = `${label} · +${String(Date.now() - started)} ms`;
23951
+ moments.push({
23952
+ label: at,
23953
+ png
23954
+ });
23955
+ changes.push({
23956
+ label: at,
23957
+ changed: (await comparePictures(page, dataUrl(before), dataUrl(png))).changed
23958
+ });
23959
+ };
23960
+ for (const [index, step] of steps.entries()) try {
23961
+ if (step.kind === "click" || step.kind === "type") {
23962
+ const element = page.locator(selectorOf(step.target)).first();
23963
+ if (await element.count() === 0) return { problem: `Step ${String(index + 1)}: ${step.target} is not on the page — its name or a CSS selector.` };
23964
+ await element.click({ timeout: 3e3 });
23965
+ if (step.kind === "type") await page.keyboard.type(step.text);
23966
+ } else if (step.kind === "press") await page.keyboard.press(step.key);
23967
+ else if (step.kind === "wait") await page.waitForTimeout(step.ms);
23968
+ else if (step.kind === "wait-for") await page.waitForSelector(selectorOf(step.target), { timeout: 1e4 });
23969
+ else if (step.kind === "shot") await picture(step.label ?? `step ${String(index + 1)}`);
23970
+ else for (let frame = 1; frame <= step.count; frame += 1) {
23971
+ if (frame > 1) await page.waitForTimeout(step.every);
23972
+ await picture(`frame ${String(frame)}/${String(step.count)}`);
23973
+ }
23974
+ } catch (error) {
23975
+ return { problem: `Step ${String(index + 1)} (${stepText(step)}) did not happen: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}` };
23976
+ }
23977
+ return {
23978
+ moments,
23979
+ changes
23980
+ };
23981
+ };
23719
23982
  var shot = async (route, options) => {
23720
23983
  const project = await projectHere("whose page to picture");
23721
23984
  if (!project) return;
@@ -23724,20 +23987,53 @@ var shot = async (route, options) => {
23724
23987
  fail(where.problem);
23725
23988
  return;
23726
23989
  }
23990
+ const account = options.as === void 0 ? void 0 : await checkAccount(project.root, options.as);
23991
+ if (account && "problem" in account) {
23992
+ fail(`--as ${account.problem}.`);
23993
+ return;
23994
+ }
23727
23995
  const browser = await launchBrowser(project.root);
23728
23996
  if ("problem" in browser) {
23729
23997
  fail(browser.problem);
23730
23998
  return;
23731
23999
  }
23732
24000
  const clicks = options.click ?? [];
24001
+ const script = options.steps?.startsWith("@") ? await readFile(path.resolve(options.steps.slice(1)), "utf-8").catch(() => void 0) : options.steps;
24002
+ if (options.steps !== void 0 && script === void 0) {
24003
+ await browser.close();
24004
+ fail(`${options.steps.slice(1)} cannot be read: --steps @<file> names a file of steps.`);
24005
+ return;
24006
+ }
24007
+ const steps = script === void 0 ? void 0 : parseSteps(script, options.every ?? 500);
24008
+ if (steps && "problem" in steps) {
24009
+ await browser.close();
24010
+ fail(`--steps: ${steps.problem}.`);
24011
+ return;
24012
+ }
24013
+ if (steps && (clicks.length > 0 || options.compare || (options.frames ?? 0) > 1)) {
24014
+ await browser.close();
24015
+ fail("`--steps` is the whole interaction — its own clicks and frames: leave out --click, --frames and --compare.");
24016
+ return;
24017
+ }
23733
24018
  if (options.compare && clicks.length > 0) {
23734
24019
  await browser.close();
23735
24020
  fail("`--compare` sets the page beside another site as it loads: leave out --click, or picture the click on its own.");
23736
24021
  return;
23737
24022
  }
23738
- if (options.sheet && clicks.length === 0 && (options.frames ?? 0) <= 1) {
24023
+ if (options.sheet && clicks.length === 0 && (options.frames ?? 0) <= 1 && options.steps === void 0) {
24024
+ await browser.close();
24025
+ fail("`--sheet` lays out the pictures a --click, --frames or --steps takes: with none of them, there is one picture and no sheet.");
24026
+ return;
24027
+ }
24028
+ const fromLoad = options.from === "load";
24029
+ if (fromLoad && ((options.frames ?? 0) <= 1 || clicks.length > 0 || options.compare)) {
24030
+ await browser.close();
24031
+ 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.");
24032
+ return;
24033
+ }
24034
+ if (fromLoad && !options.clip && !options.scrollTo && !options.viewport) {
23739
24035
  await browser.close();
23740
- fail("`--sheet` lays out the pictures a --click or --frames takes: with neither, there is one picture and no sheet.");
24036
+ 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
24037
  return;
23742
24038
  }
23743
24039
  if (options.compare && (options.clip || options.scrollTo || options.viewport)) {
@@ -23756,12 +24052,14 @@ var shot = async (route, options) => {
23756
24052
  ...options.clip ? { clip: options.clip } : {},
23757
24053
  ...options.scrollTo ? { scrollTo: options.scrollTo } : {},
23758
24054
  ...options.viewport ? { viewport: true } : {},
24055
+ ...account ? { account } : {},
24056
+ ...fromLoad ? { fromLoad } : {},
23759
24057
  still: !options.clip && !options.scrollTo && !options.viewport && (options.frames ?? 0) <= 1 && clicks.length === 0
23760
24058
  };
23761
24059
  const how = framing(view);
23762
24060
  const taken = await pictureOf(browser, `${where.origin}${pathname}`, view, where.origin);
23763
24061
  if ("problem" in taken) {
23764
- fail(`${taken.problem} Start the project first: npm start`);
24062
+ fail(taken.problem);
23765
24063
  return;
23766
24064
  }
23767
24065
  const name = pathname === "/" ? "home" : pathname.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "");
@@ -23776,7 +24074,8 @@ var shot = async (route, options) => {
23776
24074
  scheme: taken.scheme,
23777
24075
  out,
23778
24076
  ...taken.settled > 0 ? { settled: taken.settled } : {},
23779
- ...taken.unloaded > 0 ? { unloaded: taken.unloaded } : {}
24077
+ ...taken.unloaded > 0 ? { unloaded: taken.unloaded } : {},
24078
+ ...taken.sentTo ? { sentTo: taken.sentTo } : {}
23780
24079
  };
23781
24080
  if (options.compare) {
23782
24081
  const other = new URL(options.compare);
@@ -23790,7 +24089,15 @@ var shot = async (route, options) => {
23790
24089
  const frames = options.frames ?? 0;
23791
24090
  const every = options.every ?? 500;
23792
24091
  let moments = [];
23793
- if (clicks.length > 0) {
24092
+ if (steps) {
24093
+ const played = await playSteps(taken, how, steps);
24094
+ if ("problem" in played) {
24095
+ fail(played.problem);
24096
+ return;
24097
+ }
24098
+ moments = played.moments;
24099
+ report.steps = played.changes;
24100
+ } else if (clicks.length > 0) {
23794
24101
  const played = await clickThrough(taken, how, clicks, {
23795
24102
  frames,
23796
24103
  every
@@ -23808,7 +24115,7 @@ var shot = async (route, options) => {
23808
24115
  pictures.push(await capture(taken.page, how));
23809
24116
  }
23810
24117
  moments = pictures.map((png, frame) => ({
23811
- label: frame === 0 ? "before" : `+${String(frame * every)} ms`,
24118
+ label: frame === 0 ? fromLoad ? "HTML in" : "before" : `+${String(frame * every)} ms`,
23812
24119
  png
23813
24120
  }));
23814
24121
  const diffs = [];
@@ -23832,7 +24139,7 @@ var shot = async (route, options) => {
23832
24139
  })));
23833
24140
  report.sheet = `${base}-sheet.png`;
23834
24141
  await writeFile(report.sheet, Buffer.from(sheet.replace(/^data:image\/png;base64,/, ""), "base64"));
23835
- } else if (moments.length > 1 && clicks.length > 0) {
24142
+ } else if (moments.length > 1 && (clicks.length > 0 || steps)) {
23836
24143
  report.pictures = [];
23837
24144
  for (const [index, moment] of moments.slice(1).entries()) {
23838
24145
  const file = `${base}-${String(index + 1)}.png`;
@@ -23848,9 +24155,11 @@ var shot = async (route, options) => {
23848
24155
  return;
23849
24156
  }
23850
24157
  console.log(out);
24158
+ 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
24159
  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
24160
  if (report.unloaded) console.log(chalk.yellow(`${String(report.unloaded)} picture(s) had not loaded when the page was taken`));
23853
24161
  if (report.compare) console.log(compareText(report.compare).join("\n"));
24162
+ 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
24163
  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
24164
  if (report.sheet) console.log(`sheet: ${report.sheet}`);
23856
24165
  for (const picture of report.pictures ?? []) console.log(`${picture.label}: ${picture.out}`);
@@ -23961,30 +24270,39 @@ var verify = async (options) => {
23961
24270
  };
23962
24271
  var NOT_STARTED = "start it (npm start) and verify again, or --no-pages to leave them";
23963
24272
  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
24273
+ const found = await staticPaths(project.root);
24274
+ if ("problem" in found) return found;
24275
+ const { paths, schemes } = found;
24276
+ const inEveryTheme = async (routes, account) => {
24277
+ const all = [];
24278
+ for (const scheme of schemes) {
24279
+ const reports = await checkRoutes(project, routes, {
24280
+ widths: WIDTHS,
24281
+ asked: { state: false },
24282
+ ssr: false,
24283
+ scheme,
24284
+ ...account ? { account } : {}
24285
+ });
24286
+ if ("problem" in reports) return { problem: `${reports.problem} — ${NOT_STARTED}` };
24287
+ all.push(...reports);
24288
+ }
24289
+ return all;
23970
24290
  };
23971
- const reports = await checkRoutes(project, paths, options);
23972
- if ("problem" in reports) return { problem: `${reports.problem} — ${NOT_STARTED}` };
24291
+ const reports = await inEveryTheme(paths);
24292
+ if ("problem" in reports) return reports;
23973
24293
  const sentAway = [...new Set(reports.filter(redirected).map((report) => report.path))];
23974
- if (sentAway.length === 0) return summary(reports);
24294
+ if (sentAway.length === 0) return summary(schemes, reports);
23975
24295
  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], {
24296
+ if (!account || "problem" in account) return summary(schemes, reports, account ?? { none: true });
24297
+ const signedIn = await inEveryTheme(sentAway, account);
24298
+ if ("problem" in signedIn) return signedIn;
24299
+ return summary(schemes, [...reports.filter((report) => !sentAway.includes(report.path)), ...signedIn], {
23983
24300
  as: account.username,
23984
24301
  checks: signedIn.filter((report) => !unchecked(report)).length
23985
24302
  });
23986
24303
  };
23987
- var summary = (reports, signedIn) => ({
24304
+ var summary = (schemes, reports, signedIn) => ({
24305
+ schemes,
23988
24306
  passing: reports.filter((report) => report.ok).length,
23989
24307
  failing: reports.filter((report) => !report.ok && !unchecked(report)),
23990
24308
  unchecked: [...new Set(reports.filter(unchecked).map((report) => report.path))],
@@ -24003,7 +24321,7 @@ var verifyText = (results, pages) => [...results.flatMap((result) => result.ok ?
24003
24321
  ...(result.said ?? []).map((line) => ` ${line}`),
24004
24322
  ...result.fix ? [` → ${result.fix}`] : []
24005
24323
  ]), ...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`),
24324
+ 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
24325
  ...pages.failing.map((report) => reportText$2(report, { state: false })),
24008
24326
  ...pages.unchecked.length > 0 ? [chalk.yellow(uncheckedText(pages))] : []
24009
24327
  ]].join("\n");
@@ -27192,7 +27510,7 @@ pageGroup.command("import").argument("<url>", "The page to start from: https://e
27192
27510
  768,
27193
27511
  390
27194
27512
  ])).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));
27513
+ 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("--json", "One object, for a tool or an agent").action((path, options) => shot(path, options));
27196
27514
  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
27515
  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
27516
  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));