@plitzi/cli 0.38.10 → 0.38.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14,8 +14,8 @@ import readline from "node:readline/promises";
14
14
  import { EXPLAIN_KINDS, PAGE_SPEC_FIELDS, PAGE_TEXT_LIMIT, actionSpecFromEntry, actionToSource, authorSpace, blankTemplateFiles, catalogTemplateFiles, closest, comparePictures, compareTexts, darkScheme, dataIssues, elementTriggers, elementTypeNames, emptyTemplateFiles, explain, explainKindOf, explainList, explanationText, failedFlowText, importProbe, importedFiles, inspectDocument, inspectPage, isSuggestionCode, loadImages, locateClasses, locateElements, openPage, pageRegions, pageTexts, planFixes, readDevTools, refusalOf, unrollPage } from "@plitzi/sdk-authoring";
15
15
  import { existsSync, readFileSync, readdirSync, realpathSync, statSync, watch } from "node:fs";
16
16
  import { parseEnv, promisify } from "node:util";
17
- import { lookup } from "node:dns/promises";
18
17
  import esbuild, { build } from "esbuild";
18
+ import { lookup } from "node:dns/promises";
19
19
  import { unzipSync, zipSync } from "fflate";
20
20
  import { gzipSync } from "node:zlib";
21
21
  import { emitKeypressEvents } from "node:readline";
@@ -24,6 +24,9 @@ var t$5 = `[data-plitzi-devtools] { display: none !important; }`;
24
24
  //#region ../../packages/sdk-shared/dist/helpers/isRecord.mjs
25
25
  var e$3 = (e) => typeof e == "object" && !!e && !Array.isArray(e);
26
26
  //#endregion
27
+ //#region ../../packages/sdk-shared/dist/realtime/topics.mjs
28
+ var m$2 = "plitzi-observer";
29
+ //#endregion
27
30
  //#region ../../packages/sdk-shared/dist/theme/themeCookie.mjs
28
31
  var t$3 = "theme";
29
32
  //#endregion
@@ -148,11 +151,15 @@ var openProjectPage = async (browser, origin, view) => {
148
151
  colorScheme: view.scheme ?? "light",
149
152
  reducedMotion: view.reducedMotion ? "reduce" : "no-preference"
150
153
  });
151
- if (view.scheme) await page.context().addCookies([{
154
+ await page.context().addCookies([...view.scheme ? [{
152
155
  name: t$3,
153
156
  value: view.scheme,
154
157
  url: origin
155
- }]);
158
+ }] : [], ...view.presence ? [] : [{
159
+ name: m$2,
160
+ value: "1",
161
+ url: origin
162
+ }]]);
156
163
  return page;
157
164
  };
158
165
  /**
@@ -1405,7 +1412,7 @@ var o$4 = () => ({
1405
1412
  //#region src/scaffold/pluginPackage/names.ts
1406
1413
  var PREFIX = "plitzi-plugin-";
1407
1414
  /** npm's own rule for a package name, scope included. */
1408
- var PACKAGE_NAME = /^(@[a-z0-9][a-z0-9-._~]*\/)?[a-z0-9][a-z0-9-._~]*$/;
1415
+ var PACKAGE_NAME$1 = /^(@[a-z0-9][a-z0-9-._~]*\/)?[a-z0-9][a-z0-9-._~]*$/;
1409
1416
  /** What the name has to leave once the scope and prefix are off: words of letters and digits, starting with a letter. */
1410
1417
  var BASE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
1411
1418
  var baseOf = (packageName) => {
@@ -1414,7 +1421,7 @@ var baseOf = (packageName) => {
1414
1421
  };
1415
1422
  /** What is wrong with a name as a plugin's, in words that say how to fix it — or `undefined` when nothing is. */
1416
1423
  var pluginNameProblem = (packageName) => {
1417
- if (!PACKAGE_NAME.test(packageName)) return `"${packageName}" is not a package name npm accepts: lowercase letters, digits and dashes, optionally under a @scope/.`;
1424
+ if (!PACKAGE_NAME$1.test(packageName)) return `"${packageName}" is not a package name npm accepts: lowercase letters, digits and dashes, optionally under a @scope/.`;
1418
1425
  if (!BASE.test(baseOf(packageName))) return `"${packageName}" has to name the plugin in words — lowercase letters and digits between single dashes, starting with a letter (seat-picker, chart2) — since the component and the type a space uses are made from it.`;
1419
1426
  const { type } = pluginNames(packageName);
1420
1427
  if (elementTypeNames.includes(type)) return `"${packageName}" would make the type "${type}", which is a built-in element: a space could not tell the two apart. Name it after what it does (seat-picker, sales-chart).`;
@@ -2512,7 +2519,7 @@ var YARN_IGNORES$1 = "\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases
2512
2519
  * the CLI records about it, and is committed — a clone without it could not pull, push or upgrade.
2513
2520
  */
2514
2521
  var gitignore$1 = ({ mode, packageManager }) => `node_modules\n${v$3}\n.env\ntmp\n${mode === "server" ? `${y$2}\n` : ""}${packageManager === "yarn" ? YARN_IGNORES$1 : ""}`;
2515
- var startLine = ({ mode, packageManager, source }) => mode === "server" ? `\`${runCommand(packageManager, "start")}\` serves pages on http://127.0.0.1:8080 — or, when something else holds 8080, the next free port, which it prints and writes to \`${x$3}\` (set \`PORT\` to choose one). \`${runCommand(packageManager, "start:dev")}\` restarts on a save to the server's code; a save to a plugin swaps it in the open page${source === "local" ? ", and a save to the space reloads it" : ""}. In production, \`${runCommand(packageManager, "build")}\` once and run \`${runCommand(packageManager, "start:prod")}\`, which sets \`NODE_ENV=production\` itself: the compiled server, with no TypeScript in the process.` : `\`${runCommand(packageManager, "start")}\` runs Vite on http://127.0.0.1:5173, with hot module replacement.`;
2522
+ var startLine = ({ mode, packageManager, source }) => mode === "server" ? `\`${runCommand(packageManager, "start")}\` serves pages on http://127.0.0.1:8080 — or, when something else holds 8080, the next free port, which it prints and writes to \`${x$3}\` (set \`PORT\` to choose one; \`HOST=0.0.0.0\` opens it to your network — a tablet on the same Wi-Fi — and prints the address to open there). \`${runCommand(packageManager, "start:dev")}\` restarts on a save to the server's code; a save to a plugin swaps it in the open page${source === "local" ? ", and a save to the space reloads it" : ""}. In production, \`${runCommand(packageManager, "build")}\` once and run \`${runCommand(packageManager, "start:prod")}\`, which sets \`NODE_ENV=production\` itself: the compiled server, with no TypeScript in the process.` : `\`${runCommand(packageManager, "start")}\` runs Vite on http://127.0.0.1:5173, with hot module replacement.`;
2516
2523
  var spaceSection = (answers) => {
2517
2524
  if (answers.source === "cloud") return `## Where the space comes from
2518
2525
 
@@ -2724,6 +2731,12 @@ PLITZI_SIGNING_SECRET=
2724
2731
 
2725
2732
  # Left out: 8080, or the next free port while developing.
2726
2733
  # PORT=8080
2734
+
2735
+ # Left out: this machine only. 0.0.0.0 opens it to the network — a tablet on the same Wi-Fi, and anyone else on it.
2736
+ # HOST=0.0.0.0
2737
+
2738
+ # The secrets the space's actions and functions name — ctx.fetch's credential, a connector's: credential id → its keys.
2739
+ # PLITZI_CREDENTIALS={"google":{"clientId":"…","clientSecret":"…"}}
2727
2740
  `;
2728
2741
  /** A `.env` with the signing key `plitzi create` made for the project in its place. */
2729
2742
  var withSigningSecret = (env, secret) => env.replace(/^PLITZI_SIGNING_SECRET=$/m, `PLITZI_SIGNING_SECRET=${secret}`);
@@ -6742,7 +6755,8 @@ var n$33 = t$60()({
6742
6755
  data: "",
6743
6756
  from: "",
6744
6757
  user: "",
6745
- at: ""
6758
+ at: "",
6759
+ echo: !1
6746
6760
  }
6747
6761
  },
6748
6762
  onJoin: {
@@ -6784,6 +6798,11 @@ var n$33 = t$60()({
6784
6798
  label: "Data (JSON or a template)",
6785
6799
  defaultValue: "",
6786
6800
  type: "text"
6801
+ },
6802
+ echo: {
6803
+ label: "Heard on this page too",
6804
+ defaultValue: !1,
6805
+ type: "boolean"
6787
6806
  }
6788
6807
  }
6789
6808
  },
@@ -6809,7 +6828,7 @@ var n$33 = t$60()({
6809
6828
  definition: {
6810
6829
  label: "Channel",
6811
6830
  type: "channel",
6812
- description: "A realtime channel: every page on the same `topic` hears what any of them publishes, within milliseconds. The topic must match a channel the space declares in its settings (`channels`), e.g. `board:{{ id }}` under `board:{id}`. A topic of a private channel (`grant: true`) opens only with `grant`, bound to the grant the action that let this visitor in returned (its `realtime.grant` step). ITS DESCENDANTS bind to its source `channel_<id>`: `connected`, `members` (with the state each announced), `messages` (the last `keep`) and `last`. It fires `onMessage`; `onJoin` when somebody who came after this page has announced who they are, and `onLeave` when somebody goes — each with `from`, `user` and the `state` they announced; and answers `publish({ type, data })` and `setPresence({ data })`; `presence` is the state this page announces (a name, a colour). Closed in the builder and anywhere without a server.",
6831
+ description: "A realtime channel: every OTHER page on the same `topic` hears what one publishes, within milliseconds — the page that sent it too with `echo` (its `from` is then the channel's `me`). The topic must match a channel the space declares in its settings (`channels`), e.g. `board:{{ id }}` under `board:{id}`. A topic of a private channel (`grant: true`) opens only with `grant`, bound to the grant the action that let this visitor in returned (its `realtime.grant` step). ITS DESCENDANTS bind to its source `channel_<id>`: `connected`, `members` (with the state each announced), `messages` (the last `keep`) and `last`. It fires `onMessage`; `onJoin` when somebody who came after this page has announced who they are, and `onLeave` when somebody goes — each with `from`, `user` and the `state` they announced; and answers `publish({ type, data, echo })` and `setPresence({ data })`; `presence` is the state this page announces (a name, a colour). Closed in the builder and anywhere without a server.",
6813
6832
  items: [],
6814
6833
  bindings: {},
6815
6834
  styleSelectors: { base: "" },
@@ -7980,10 +7999,15 @@ var AUTHORING_CODES = {
7980
7999
  means: "a template in a page's `seoTitle`, `seoDescription` or `notFound` that reads what is not there when the server answers — anything but the page's `runtime: 'server'` providers and `navigation` — or cannot be read",
7981
8000
  fix: "read the record from a provider with `runtime: 'server'` on the page or its layout — `'{{ apiContainer_post.title }} — Blog'` — or words of its own"
7982
8001
  },
8002
+ "declared-callback-target": {
8003
+ kind: "refused",
8004
+ means: "a `declaredCallback` target with a key beside `on` and `params` — read nowhere, so the step runs without it",
8005
+ fix: "the value in `params`: `{ on: 'calendar', params: { date } }`"
8006
+ },
7983
8007
  "folder-undeclared": {
7984
8008
  kind: "refused",
7985
8009
  means: "a page, a layout or a folder filed in a folder the space does not declare",
7986
- fix: "declare the folder, or the name it suggests"
8010
+ fix: "declare it on the space — `pageFolders: [{ id: 'docs', name: 'Docs' }]`, its slug starting its pages' addresses — or use the name it suggests"
7987
8011
  },
7988
8012
  "folder-cycle": {
7989
8013
  kind: "refused",
@@ -8080,6 +8104,11 @@ var AUTHORING_CODES = {
8080
8104
  means: "a `slots` key the element type does not have — its class would dress nothing",
8081
8105
  fix: "one of the slots `plitzi explain <type>` lists; the element itself takes `class`"
8082
8106
  },
8107
+ "slot-not-class": {
8108
+ kind: "refused",
8109
+ means: "a `slots` entry that is not a class — rules written in place, or nothing — which a slot cannot wear",
8110
+ fix: "the rules as a `styles()` declaration, handed to the slot beside its other classes"
8111
+ },
8083
8112
  "class-conflict": {
8084
8113
  kind: "refused",
8085
8114
  means: "two different declarations for one class name",
@@ -8093,7 +8122,7 @@ var AUTHORING_CODES = {
8093
8122
  "class-and-css": {
8094
8123
  kind: "refused",
8095
8124
  means: "an element wearing a shared class AND `css` or `states` of its own — it has one base selector",
8096
- fix: "the rules on top of the class: `class: [card, { opacity: '0.5' }]` (needs an `id`) — or into the class"
8125
+ fix: "the rules on top of the class: `class: [card, { opacity: '0.5' }]` — or into the class"
8097
8126
  },
8098
8127
  "class-and-selector": {
8099
8128
  kind: "refused",
@@ -8105,11 +8134,6 @@ var AUTHORING_CODES = {
8105
8134
  means: "more than one set of rules in a class list",
8106
8135
  fix: "one set, after the classes: `class: [card, { opacity: '0.5', 'margin-top': '8px' }]`"
8107
8136
  },
8108
- "modifier-without-id": {
8109
- kind: "refused",
8110
- means: "rules on top of a class on an element with no `id` — the id names the class they become",
8111
- fix: "`id: 'hero-bg'`"
8112
- },
8113
8137
  "selector-invalid": {
8114
8138
  kind: "refused",
8115
8139
  means: "a `selector` that is not a CSS class name",
@@ -8390,6 +8414,11 @@ var AUTHORING_CODES = {
8390
8414
  means: "a `runtime: 'server'` element inside a component: the page server resolves a page's and its layouts', never a component's",
8391
8415
  fix: "put the provider on the page, around the instance, and hand the component its rows as a prop"
8392
8416
  },
8417
+ "template-in-value": {
8418
+ kind: "refused",
8419
+ means: "a `{{ }}` inside an attribute that is an object or a list — a provider's `input`, a list's `items`, a plugin's settings: only an attribute that is text is evaluated",
8420
+ fix: "a binding on the attribute (`bindTemplate(name, source, template, { returns: 'value' })`); for a provider's `input`, nothing for a route or query param — the action is already handed them as `input.<name>`"
8421
+ },
8393
8422
  "not-found-in-browser": {
8394
8423
  kind: "refused",
8395
8424
  means: "`notFound` on a provider asked from the browser — its answer arrives after the page was sent with its status",
@@ -9002,7 +9031,11 @@ var sourceRow = (listSource, items) => {
9002
9031
  const shape = Reflect.get(items, SHAPE);
9003
9032
  return {
9004
9033
  item: pathTo(`${listSource}.item`, Array.isArray(shape) ? itemShape(shape) : UNKNOWN),
9005
- index: pathTo(`${listSource}.index`, 0)
9034
+ index: pathTo(`${listSource}.index`, 0),
9035
+ inTemplate: {
9036
+ item: `${listSource}.item`,
9037
+ index: `${listSource}.index`
9038
+ }
9006
9039
  };
9007
9040
  };
9008
9041
  var e$39 = [
@@ -10026,19 +10059,22 @@ var openModal = (target, metadata) => ({
10026
10059
  params: metadata === void 0 ? {} : { metadata }
10027
10060
  });
10028
10061
  /**
10029
- * Says something on a `channel`, by id: every page on its topic hears it — `onMessage`, and the channel's source.
10062
+ * Says something on a `channel`, by id: every OTHER page on its topic hears it — `onMessage`, and the channel's source.
10063
+ * The page that said it hears it too with `{ echo: true }` — a reaction, a chat line: what the sender shows the moment
10064
+ * it sends — once the server took it, its `from` the channel's `me`.
10030
10065
  *
10031
10066
  * `data` is what they receive: a template (`'{{ state.draft }}'`) or JSON text. `type` is yours to name — `chat`,
10032
10067
  * `wave`, `move` — and a flow on `onMessage` tells them apart with `when({ field: '<step>.type', … })`.
10033
10068
  */
10034
- var publishOn = (target, type, data = null) => ({
10069
+ var publishOn = (target, type, data = null, { echo = false } = {}) => ({
10035
10070
  type: "callback",
10036
10071
  action: "publish",
10037
10072
  title: "Publish",
10038
10073
  on: target,
10039
10074
  params: {
10040
10075
  type,
10041
- data: typeof data === "string" ? data : JSON.stringify(data)
10076
+ data: typeof data === "string" ? data : JSON.stringify(data),
10077
+ ...echo ? { echo } : {}
10042
10078
  }
10043
10079
  });
10044
10080
  /** Announces this page on a `channel` with presence — a name, a colour: what the other members see of it. */
@@ -10145,6 +10181,12 @@ var declaredTrigger = (declaration, trigger) => {
10145
10181
  */
10146
10182
  var declaredCallback = (declaration, callback, target) => {
10147
10183
  const declared = declaration.callbacks?.[callback];
10184
+ const stray = Object.keys(target).filter((key) => key !== "on" && key !== "params");
10185
+ if (stray.length > 0) {
10186
+ const named = Object.keys(declared?.params ?? {});
10187
+ const moved = stray.map((key) => `${key}: ${JSON.stringify(Reflect.get(target, key))}`).join(", ");
10188
+ throw new AuthoringError("declared-callback-target", `declaredCallback(…, '${callback}') is given ${stray.map((key) => `"${key}"`).join(", ")} beside \`on\`, where nothing reads it${stray.every((key) => named.includes(key)) ? ` — ${stray.length === 1 ? "it is a param" : "they are params"} of "${callback}"` : ""}: a callback's values go in \`params\` — \`{ on: '${target.on}', params: { ${moved} } }\`.`);
10189
+ }
10148
10190
  return {
10149
10191
  type: "callback",
10150
10192
  action: declared?.action ?? callback,
@@ -10874,525 +10916,73 @@ var e$9 = "/data/";
10874
10916
  var e$7 = (e) => typeof e == "string" && /\{\{[\s\S]*?\}\}|\{%[\s\S]*?%\}/.test(e);
10875
10917
  var i$5 = (e) => `value|round(0, '${e}')`;
10876
10918
  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));
10878
- /** A container with no tag of its own named: the row `list-row-not-li` makes an `<li>` without asking. */
10879
- var isPlainContainer = (element) => element.definition.type === "container" && (element.attributes.subType === void 0 || element.attributes.subType === "div");
10880
- new Set(STEP_TYPES);
10881
- /** A step's params that name a path under `runtime.state`, by action. */
10882
- var STATE_PATH_PARAMS = {
10883
- setState: ["key"],
10884
- toggleState: ["key"],
10885
- appendState: ["key"],
10886
- removeState: ["key"],
10887
- toggleInState: ["key"],
10888
- clearState: ["key"],
10889
- moveState: ["from", "to"]
10919
+ /** Every kebab-case CSS property Plitzi's style engine understands. Values written to a definition must use these
10920
+ * exact keys — camelCase or unknown keys are rejected. */
10921
+ var cssProperties = Array.from(new Set(Object.values(t$30))).sort();
10922
+ var cssPropertySet = new Set(cssProperties);
10923
+ var isCssProperty = (key) => cssPropertySet.has(key);
10924
+ var toKebab = (key) => key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
10925
+ /**
10926
+ * A property as an author may write it, in the spelling the document keeps: kebab-case already, or camelCase the way a
10927
+ * React style object writes it — `paddingTop` is `padding-top`, `WebkitLineClamp` is `-webkit-line-clamp`. A custom
10928
+ * property (`--brand`) is the author's own spelling and is kept.
10929
+ */
10930
+ var cssPropertyName = (key) => {
10931
+ if (isCustomProperty(key) || !/[A-Z]/.test(key)) return key;
10932
+ return /^(Webkit|Moz|Ms|O)(?=[A-Z])/.exec(key) ? `-${toKebab(key)}` : toKebab(key);
10890
10933
  };
10891
- var t$6 = [
10892
- "fade",
10893
- "fade-up",
10894
- "fade-down",
10895
- "slide-left",
10896
- "slide-right",
10897
- "slide-up",
10934
+ /**
10935
+ * The properties a bare number is a whole value of — a weight, an opacity, a count, a ratio. Anywhere else a number is
10936
+ * a length, and a length without a unit is no value at all: `gap: 16` is `16px`.
10937
+ */
10938
+ var UNITLESS = /* @__PURE__ */ new Set([
10939
+ "opacity",
10940
+ "z-index",
10941
+ "font-weight",
10942
+ "line-height",
10943
+ "flex",
10944
+ "flex-grow",
10945
+ "flex-shrink",
10946
+ "order",
10947
+ "column-count",
10948
+ "aspect-ratio",
10949
+ "orphans",
10950
+ "widows",
10951
+ "zoom",
10898
10952
  "scale",
10899
- "zoom-in",
10900
- "zoom-out",
10901
- "pop",
10902
- "bounce-in",
10903
- "tilt",
10904
- "spin-in"
10905
- ];
10906
- var n$6 = [
10907
- "load",
10908
- "view",
10909
- "scroll"
10910
- ];
10911
- var r$4 = [
10912
- "float",
10913
- "pulse",
10914
- "spin",
10915
- "sway",
10916
- "bounce",
10917
- "wobble",
10918
- "orbit",
10919
- "heartbeat",
10920
- "drift"
10921
- ];
10922
- var o$6 = {
10923
- fade: [{
10924
- at: 0,
10925
- opacity: 0
10926
- }],
10927
- "fade-up": [{
10928
- at: 0,
10929
- opacity: 0,
10930
- translate: "0 24px"
10931
- }],
10932
- "fade-down": [{
10933
- at: 0,
10934
- opacity: 0,
10935
- translate: "0 -24px"
10936
- }],
10937
- "slide-left": [{
10938
- at: 0,
10939
- opacity: 0,
10940
- translate: "32px 0"
10941
- }],
10942
- "slide-right": [{
10943
- at: 0,
10944
- opacity: 0,
10945
- translate: "-32px 0"
10946
- }],
10947
- "slide-up": [{
10948
- at: 0,
10949
- opacity: 0,
10950
- translate: "0 72px"
10951
- }],
10952
- scale: [{
10953
- at: 0,
10954
- opacity: 0,
10955
- scale: "0.94"
10956
- }],
10957
- "zoom-in": [{
10958
- at: 0,
10959
- opacity: 0,
10960
- scale: "0.6"
10961
- }],
10962
- "zoom-out": [{
10963
- at: 0,
10964
- opacity: 0,
10965
- scale: "1.25"
10966
- }],
10967
- pop: [
10968
- {
10969
- at: 0,
10970
- opacity: 0,
10971
- scale: "0.5"
10972
- },
10973
- {
10974
- at: 55,
10975
- opacity: 1,
10976
- scale: "1.08"
10977
- },
10978
- {
10979
- at: 78,
10980
- scale: "0.97"
10981
- }
10982
- ],
10983
- "bounce-in": [
10984
- {
10985
- at: 0,
10986
- opacity: 0,
10987
- translate: "0 -64px"
10988
- },
10989
- {
10990
- at: 55,
10991
- opacity: 1,
10992
- translate: "0 10px"
10993
- },
10994
- {
10995
- at: 75,
10996
- translate: "0 -5px"
10997
- },
10998
- {
10999
- at: 90,
11000
- translate: "0 2px"
11001
- }
11002
- ],
11003
- tilt: [{
11004
- at: 0,
11005
- opacity: 0,
11006
- translate: "0 32px",
11007
- rotate: "-6deg"
11008
- }],
11009
- "spin-in": [{
11010
- at: 0,
11011
- opacity: 0,
11012
- scale: "0.4",
11013
- rotate: "-180deg"
11014
- }]
11015
- };
11016
- Object.fromEntries(t$6.map((e) => {
11017
- let [{ at: t, ...n }] = o$6[e];
11018
- return [e, n];
11019
- }));
11020
- var c$4 = (e, t) => {
11021
- let n = t * 2 * Math.PI, r = Math.round(Math.sin(n) * e * 10) / 10, i = Math.round(-Math.cos(n) * e * 10) / 10;
11022
- return `translate(${String(r)}px, ${String(i)}px)`;
10953
+ "tab-size",
10954
+ "animation-iteration-count",
10955
+ "grid-row-start",
10956
+ "grid-row-end",
10957
+ "grid-column-start",
10958
+ "grid-column-end",
10959
+ "fill-opacity",
10960
+ "stroke-opacity",
10961
+ "stroke-width",
10962
+ "-webkit-line-clamp"
10963
+ ]);
10964
+ /** A number written as a value, as CSS reads it: itself where a bare number is a whole value, pixels otherwise. */
10965
+ var cssNumberValue = (property, value) => UNITLESS.has(property) || isCustomProperty(property) || value === 0 ? value : `${String(value)}px`;
10966
+ /** If a camelCase key maps to a known kebab-case property, return it — used to name the correct key in an error. */
10967
+ var suggestCssProperty = (key) => {
10968
+ const kebab = toKebab(key);
10969
+ return cssPropertySet.has(kebab) ? kebab : void 0;
11023
10970
  };
11024
- Array.from({ length: 9 }, (e, t) => ({
11025
- at: t * 100 / 8,
11026
- transform: c$4(6, t / 8)
11027
- }));
11028
- [
11029
- "[data-motion-loop] { animation-play-state: running, running !important; }",
11030
- "[data-motion-on=\"view\"][data-motion-enter], [data-motion-on=\"view\"][data-motion-stagger] > * { animation-play-state: running, running !important; }",
11031
- "[data-motion-on=\"scroll\"][data-motion-enter], [data-motion-on=\"scroll\"][data-motion-stagger] > * { animation-timeline: auto, auto !important; }"
11032
- ].join("\n");
11033
- Object.entries(AUTHORING_CODES).filter(([, entry]) => entry.kind === "suggested").map(([code]) => code);
11034
- var URL_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
11035
- /** A page target that is really a URL: the one reading of `mailto:`/`https:` a page-mode link can never mean. */
11036
- var isUrlTarget = (target) => typeof target === "string" && !e$7(target) && URL_SCHEME.test(target);
11037
- var isRoot = (element) => !element.definition.parentId;
11038
- var stepsOf$1 = (element) => Object.values(element.definition.interactions ?? {});
11039
10971
  /**
11040
- * A key the element or step does not take, renamed to the one it was a typo of — when that one is free — or dropped:
11041
- * it was never read, so dropping it changes nothing on screen.
10972
+ * A CSS custom property — `--brand`, `--space-4`.
10973
+ *
10974
+ * Not in the vocabulary and deliberately allowed anyway: a custom property is a declaration the browser resolves,
10975
+ * not one the style editor has to offer a control for, and a space that defines its palette in one place and reads
10976
+ * it back with `var(--brand)` is doing the right thing.
11042
10977
  */
11043
- var settleUnknownKeys = (record, known, what, on, report, defaults = {}) => {
11044
- for (const key of Object.keys(record)) {
11045
- if (known.includes(key)) continue;
11046
- const wrapped = record[key];
11047
- if (e$33(wrapped) && Object.keys(wrapped).length > 0 && Object.keys(wrapped).every((inner) => known.includes(inner))) {
11048
- const inners = Object.keys(wrapped);
11049
- if (inners.every((inner) => record[inner] === void 0 || record[inner] === defaults[inner])) {
11050
- Object.assign(record, wrapped);
11051
- Reflect.deleteProperty(record, key);
11052
- report(`Moved ${inners.join(", ")} out of the ${what} "${key}", up to where ${inners.length === 1 ? "it is" : "they are"} read.`, {
11053
- on,
11054
- op: "unwrap",
11055
- key
11056
- });
11057
- }
11058
- continue;
11059
- }
11060
- const meant = closest$1(key, known);
11061
- if (meant !== void 0 && record[meant] === void 0) {
11062
- record[meant] = record[key];
11063
- report(`Renamed the ${what} "${key}" to "${meant}".`, {
11064
- on,
11065
- op: "rename",
11066
- key,
11067
- to: meant
11068
- });
11069
- } else report(`Removed the ${what} "${key}", which nothing reads.`, {
11070
- on,
11071
- op: "remove",
11072
- key
11073
- });
11074
- Reflect.deleteProperty(record, key);
11075
- }
11076
- };
11077
- /** A flag written as the words `'true'`/`'false'`, stored as the boolean it is read as. */
11078
- var asFlag = (value) => value === "true" ? true : value === "false" ? false : void 0;
11079
- var stepSpec = (catalogs, step) => {
11080
- const vocabulary = catalogs.vocabulary;
11081
- const table = step.type === "globalCallback" ? vocabulary?.globalCallbacks : step.type === "utility" ? vocabulary?.utilities : step.type === "callback" ? vocabulary?.sharedCallbacks : void 0;
11082
- return table && Object.hasOwn(table, step.action) ? table[step.action] : void 0;
11083
- };
11084
- new Set(Object.keys({
11085
- "page-target-url": (element, _ctx, report) => {
11086
- const { href, mode } = element.attributes;
11087
- if (element.definition.type === "link" && (mode ?? "page") === "page" && isUrlTarget(href)) {
11088
- element.attributes.mode = "external";
11089
- report(`The link to "${href}" now opens it as an external URL.`, {
11090
- on: "attribute",
11091
- op: "set",
11092
- key: "mode",
11093
- value: "external"
11094
- });
11095
- }
11096
- for (const step of stepsOf$1(element)) if (step.action === "navigate" && step.params.urlType === "page" && isUrlTarget(step.params.url)) {
11097
- step.params.urlType = "external";
11098
- report(`Step "${step.id}" now navigates to "${step.params.url}" as an external URL.`, {
11099
- on: { step: step.id },
11100
- op: "set",
11101
- key: "urlType",
11102
- value: "external"
11103
- });
11104
- }
11105
- },
11106
- "unknown-attribute": (element, ctx, report) => {
11107
- const names = ctx.attributeNamesFor(element);
11108
- if (!isRoot(element) && names) settleUnknownKeys(element.attributes, names, "attribute", "attribute", report, ctx.defaultsFor(element));
11109
- },
11110
- "attribute-kind": (element, ctx, report) => {
11111
- const defaults = ctx.catalogs.defaultAttributes?.[element.definition.type] ?? {};
11112
- for (const [name, value] of Object.entries(element.attributes)) {
11113
- const flag = asFlag(value);
11114
- if (typeof defaults[name] === "boolean" && flag !== void 0) {
11115
- element.attributes[name] = flag;
11116
- report(`\`${name}\` is now the boolean ${String(flag)}, not the text "${String(value)}".`, {
11117
- on: "attribute",
11118
- op: "set",
11119
- key: name,
11120
- value: flag
11121
- });
11122
- }
11123
- }
11124
- },
11125
- "step-params": (element, ctx, report) => {
11126
- for (const step of stepsOf$1(element)) {
11127
- const spec = stepSpec(ctx.catalogs, step);
11128
- if (!spec?.params) continue;
11129
- const params = spec.params;
11130
- if (spec.strictParams) settleUnknownKeys(step.params, Object.keys(params), `param of step "${step.id}"`, { step: step.id }, report);
11131
- for (const [key, value] of Object.entries(step.params)) {
11132
- const flag = asFlag(value);
11133
- if (Object.hasOwn(params, key) && params[key].type === "boolean" && flag !== void 0) {
11134
- step.params[key] = flag;
11135
- report(`Step "${step.id}": \`${key}\` is now the boolean ${String(flag)}.`, {
11136
- on: { step: step.id },
11137
- op: "set",
11138
- key,
11139
- value: flag
11140
- });
11141
- }
11142
- }
11143
- }
11144
- },
11145
- "global-callback-module": (element, ctx, report) => {
11146
- for (const step of stepsOf$1(element)) {
11147
- const declared = step.type === "globalCallback" && ctx.catalogs.vocabulary ? ctx.catalogs.vocabulary.globalCallbacks[step.action] : void 0;
11148
- if (declared && step.elementId !== declared.source) {
11149
- step.elementId = declared.source;
11150
- report(`Step "${step.id}" now runs "${step.action}" on "${declared.source}", the module that registers it.`, {
11151
- on: { step: step.id },
11152
- op: "set",
11153
- key: "on",
11154
- value: declared.source
11155
- });
11156
- }
11157
- }
11158
- },
11159
- "utility-module": (element, ctx, report) => {
11160
- for (const step of stepsOf$1(element)) {
11161
- const known = ctx.catalogs.vocabulary ? Object.hasOwn(ctx.catalogs.vocabulary.utilities, step.action) : false;
11162
- if (step.type === "utility" && known && step.elementId !== null) {
11163
- step.elementId = null;
11164
- report(`Step "${step.id}" runs the utility "${step.action}" on no element: a utility takes none.`, {
11165
- on: { step: step.id },
11166
- op: "remove",
11167
- key: "on"
11168
- });
11169
- }
11170
- }
11171
- },
11172
- "visibility-as-attribute": (element, _ctx, report) => {
11173
- const bindings = element.definition.bindings;
11174
- const moved = bindings?.attributes?.filter((binding) => binding.to === "visibility") ?? [];
11175
- if (!bindings || moved.length === 0) return;
11176
- bindings.attributes = bindings.attributes?.filter((binding) => binding.to !== "visibility");
11177
- bindings.initialState = [...bindings.initialState ?? [], ...moved];
11178
- report("The visibility binding now sets the visibility, rather than an attribute nothing reads.");
11179
- },
11180
- "binding-target-unknown": (element, ctx, report) => {
11181
- const names = ctx.attributeNamesFor(element);
11182
- const bindings = element.definition.bindings;
11183
- if (!names || !bindings?.attributes) return;
11184
- const reads = (binding) => binding.to === "visibility" || binding.to === "className" || names.includes(binding.to);
11185
- for (const binding of bindings.attributes.filter((candidate) => !reads(candidate))) report(`Removed the binding onto "${binding.to}", which a "${element.definition.type}" never reads.`, {
11186
- on: "binding",
11187
- op: "remove",
11188
- key: binding.to
11189
- });
11190
- bindings.attributes = bindings.attributes.filter(reads);
11191
- },
11192
- "unknown-transformer": (element, ctx, report) => {
11193
- const catalog = ctx.catalogs.transformers;
11194
- if (!catalog) return;
11195
- for (const binding of Object.values(element.definition.bindings ?? {}).flat()) for (const transformer of binding.transformers ?? []) {
11196
- const meant = Object.hasOwn(catalog, transformer.action) ? void 0 : closest$1(transformer.action, Object.keys(catalog));
11197
- if (meant !== void 0) {
11198
- report(`The binding of "${binding.to}" now runs "${meant}" (was "${transformer.action}").`, {
11199
- on: "binding",
11200
- op: "replace",
11201
- key: transformer.action,
11202
- to: meant
11203
- });
11204
- transformer.action = meant;
11205
- }
11206
- }
11207
- },
11208
- "list-items-ignored": (element, _ctx, report) => {
11209
- const { attributes } = element;
11210
- const bound = Object.values(element.definition.bindings ?? {}).flat().some((binding) => binding.to === "items");
11211
- const written = Array.isArray(attributes.items) && attributes.items.length > 0;
11212
- if (element.definition.type === "list" && attributes.source !== "controlled" && (bound || written)) {
11213
- attributes.source = "controlled";
11214
- report("The list now reads its items (`source: controlled`), one row per item.", {
11215
- on: "attribute",
11216
- op: "set",
11217
- key: "source",
11218
- value: "controlled"
11219
- });
11220
- }
11221
- },
11222
- "list-row-not-li": (element, ctx, report) => {
11223
- const { parentId } = element.definition;
11224
- const list = parentId ? ctx.element(parentId) : void 0;
11225
- if (list?.definition.type === "list" && list.attributes.source === "controlled" && isPlainContainer(element)) {
11226
- element.attributes.subType = "li";
11227
- report("The row is now an `<li>` (`subType: li`), an item of the list it renders in.", {
11228
- on: "attribute",
11229
- op: "set",
11230
- key: "subType",
11231
- value: "li"
11232
- });
11233
- }
11234
- },
11235
- "overlay-starts-open": (element, _ctx, report) => {
11236
- const { type, bindings } = element.definition;
11237
- const visibilityBound = (bindings?.initialState ?? []).some((binding) => binding.to === "visibility");
11238
- if ((type === "modalContainer" || type === "dialogContainer") && element.definition.initialState?.visibility !== false && !visibilityBound) {
11239
- element.definition.initialState = {
11240
- ...element.definition.initialState,
11241
- visibility: false
11242
- };
11243
- report(`The ${type} now starts closed, to be opened from a flow.`, {
11244
- on: "field",
11245
- op: "set",
11246
- key: "visible",
11247
- value: false
11248
- });
11249
- }
11250
- },
11251
- "state-key-has-runtime-prefix": (element, _ctx, report) => {
11252
- for (const step of stepsOf$1(element)) {
11253
- if (step.type !== "globalCallback" || step.elementId !== "state") continue;
11254
- for (const param of STATE_PATH_PARAMS[step.action] ?? []) {
11255
- const value = step.params[param];
11256
- if (typeof value === "string" && /^(?:runtime\.)?state\./.test(value)) {
11257
- const next = value.replace(/^(?:runtime\.)?state\./, "");
11258
- step.params[param] = next;
11259
- report(`Step "${step.id}": \`${param}\` is now "${next}" (was "${value}").`, {
11260
- on: { step: step.id },
11261
- op: "set",
11262
- key: param,
11263
- value: next
11264
- });
11265
- }
11266
- }
11267
- }
11268
- }
11269
- }));
11270
- var e$4 = ".Toastify__toast-container.plitzi-sdk-toasts";
11271
- var t$4 = [
11272
- {
11273
- selector: e$4,
11274
- fields: {
11275
- background: ["--toastify-color-light", "--toastify-color-dark"],
11276
- text: ["--toastify-text-color-light", "--toastify-text-color-dark"],
11277
- success: ["--toastify-color-success"],
11278
- danger: ["--toastify-color-error"],
11279
- warning: ["--toastify-color-warning"],
11280
- info: ["--toastify-color-info"],
11281
- radius: ["--toastify-toast-bd-radius"],
11282
- font: ["--toastify-font-family"],
11283
- shadow: ["--toastify-toast-shadow"],
11284
- padding: ["--toastify-toast-padding"],
11285
- minHeight: ["--toastify-toast-min-height"]
11286
- }
11287
- },
11288
- {
11289
- selector: `:where(${e$4} .Toastify__toast)`,
11290
- fields: {
11291
- fontSize: ["font-size"],
11292
- fontWeight: ["font-weight"],
11293
- lineHeight: ["line-height"],
11294
- border: ["border"]
11295
- }
11296
- },
11297
- {
11298
- selector: `${e$4} .Toastify__toast-icon`,
11299
- fields: {
11300
- iconSize: ["width"],
11301
- iconGap: ["margin-inline-end"]
11302
- }
11303
- },
11304
- {
11305
- selector: `${e$4} .Toastify__close-button`,
11306
- fields: { closeColor: ["color"] }
11307
- },
11308
- {
11309
- selector: `${e$4} .Toastify__close-button:not(:hover, :focus)`,
11310
- fields: { closeOpacity: ["opacity"] }
11311
- },
11312
- {
11313
- selector: `${e$4} .Toastify__progress-bar--wrp`,
11314
- fields: { progressHeight: ["height"] }
11315
- }
11316
- ];
11317
- var n$4 = t$4.flatMap((e) => Object.keys(e.fields));
11318
- var r$2$1 = (e) => n$4.includes(e);
11319
- n$4.filter(r$2$1);
11320
- var c$2$1 = (e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11321
- t$4.map((e) => {
11322
- let t = new Map(Object.entries(e.fields).flatMap(([e, t]) => r$2$1(e) ? t.map((t) => [t, e]) : []));
11323
- return {
11324
- pattern: RegExp(`(?:^|\\n)${c$2$1(e.selector)}\\s*\\{([^}]*)\\}`, "g"),
11325
- fieldOf: (e) => t.get(e)
11326
- };
11327
- });
11328
- Object.fromEntries(e$30.map((e) => [e, t$26(e)]));
11329
- /** Every kebab-case CSS property Plitzi's style engine understands. Values written to a definition must use these
11330
- * exact keys — camelCase or unknown keys are rejected. */
11331
- var cssProperties = Array.from(new Set(Object.values(t$30))).sort();
11332
- var cssPropertySet = new Set(cssProperties);
11333
- var isCssProperty = (key) => cssPropertySet.has(key);
11334
- var toKebab = (key) => key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
11335
- /**
11336
- * A property as an author may write it, in the spelling the document keeps: kebab-case already, or camelCase the way a
11337
- * React style object writes it — `paddingTop` is `padding-top`, `WebkitLineClamp` is `-webkit-line-clamp`. A custom
11338
- * property (`--brand`) is the author's own spelling and is kept.
11339
- */
11340
- var cssPropertyName = (key) => {
11341
- if (isCustomProperty(key) || !/[A-Z]/.test(key)) return key;
11342
- return /^(Webkit|Moz|Ms|O)(?=[A-Z])/.exec(key) ? `-${toKebab(key)}` : toKebab(key);
11343
- };
11344
- /**
11345
- * The properties a bare number is a whole value of — a weight, an opacity, a count, a ratio. Anywhere else a number is
11346
- * a length, and a length without a unit is no value at all: `gap: 16` is `16px`.
11347
- */
11348
- var UNITLESS = /* @__PURE__ */ new Set([
11349
- "opacity",
11350
- "z-index",
11351
- "font-weight",
11352
- "line-height",
11353
- "flex",
11354
- "flex-grow",
11355
- "flex-shrink",
11356
- "order",
11357
- "column-count",
11358
- "aspect-ratio",
11359
- "orphans",
11360
- "widows",
11361
- "zoom",
11362
- "scale",
11363
- "tab-size",
11364
- "animation-iteration-count",
11365
- "grid-row-start",
11366
- "grid-row-end",
11367
- "grid-column-start",
11368
- "grid-column-end",
11369
- "fill-opacity",
11370
- "stroke-opacity",
11371
- "stroke-width",
11372
- "-webkit-line-clamp"
11373
- ]);
11374
- /** A number written as a value, as CSS reads it: itself where a bare number is a whole value, pixels otherwise. */
11375
- var cssNumberValue = (property, value) => UNITLESS.has(property) || isCustomProperty(property) || value === 0 ? value : `${String(value)}px`;
11376
- /** If a camelCase key maps to a known kebab-case property, return it — used to name the correct key in an error. */
11377
- var suggestCssProperty = (key) => {
11378
- const kebab = toKebab(key);
11379
- return cssPropertySet.has(kebab) ? kebab : void 0;
11380
- };
11381
- /**
11382
- * A CSS custom property — `--brand`, `--space-4`.
11383
- *
11384
- * Not in the vocabulary and deliberately allowed anyway: a custom property is a declaration the browser resolves,
11385
- * not one the style editor has to offer a control for, and a space that defines its palette in one place and reads
11386
- * it back with `var(--brand)` is doing the right thing.
11387
- */
11388
- var isCustomProperty = (key) => key.startsWith("--");
11389
- var walkTopLevel = (value, onChar) => {
11390
- let depth = 0;
11391
- let quote = "";
11392
- for (const ch of value) {
11393
- if (quote) {
11394
- onChar(ch, false);
11395
- if (ch === quote) quote = "";
10978
+ var isCustomProperty = (key) => key.startsWith("--");
10979
+ var walkTopLevel = (value, onChar) => {
10980
+ let depth = 0;
10981
+ let quote = "";
10982
+ for (const ch of value) {
10983
+ if (quote) {
10984
+ onChar(ch, false);
10985
+ if (ch === quote) quote = "";
11396
10986
  continue;
11397
10987
  }
11398
10988
  if (/["']/.test(ch)) {
@@ -13704,114 +13294,566 @@ var byRange = (bucket) => {
13704
13294
  }
13705
13295
  return ranges;
13706
13296
  };
13707
- /**
13708
- * A state's or an ancestor's declarations in one range, with the parts of the element's own composed properties it
13709
- * builds on: `translate-x-2 hover:scale-105` is a hover transform that still translates.
13710
- */
13711
- var onTopOf = (own, layer) => {
13712
- const groups = new Set(Object.keys(layer).filter((key) => key.startsWith("@")).map(groupOf));
13713
- const inherited = Object.entries(own).filter(([key]) => key.startsWith("@") && groups.has(groupOf(key)));
13714
- return {
13715
- ...Object.fromEntries(inherited),
13716
- ...layer
13717
- };
13297
+ /**
13298
+ * A state's or an ancestor's declarations in one range, with the parts of the element's own composed properties it
13299
+ * builds on: `translate-x-2 hover:scale-105` is a hover transform that still translates.
13300
+ */
13301
+ var onTopOf = (own, layer) => {
13302
+ const groups = new Set(Object.keys(layer).filter((key) => key.startsWith("@")).map(groupOf));
13303
+ const inherited = Object.entries(own).filter(([key]) => key.startsWith("@") && groups.has(groupOf(key)));
13304
+ return {
13305
+ ...Object.fromEntries(inherited),
13306
+ ...layer
13307
+ };
13308
+ };
13309
+ /**
13310
+ * Desktop-first, as Plitzi writes it: the desktop rules apply everywhere, and tablet and mobile say only where they
13311
+ * differ — a property the desktop sets and a narrower range does not is `revert`, which is what "no rule here" is.
13312
+ */
13313
+ var responsive = (ranges) => {
13314
+ const { desktop } = ranges;
13315
+ const differences = (range) => {
13316
+ const changed = Object.fromEntries(Object.entries(range).filter(([property, value]) => desktop[property] !== value));
13317
+ for (const property of Object.keys(desktop)) if (!Object.hasOwn(range, property)) changed[property] = "revert";
13318
+ return changed;
13319
+ };
13320
+ const tablet = differences(ranges.tablet);
13321
+ const mobile = differences(ranges.mobile);
13322
+ if (Object.keys(tablet).length === 0 && Object.keys(mobile).length === 0) return desktop;
13323
+ return {
13324
+ ...Object.keys(desktop).length === 0 ? {} : { desktop },
13325
+ ...Object.keys(tablet).length === 0 ? {} : { tablet },
13326
+ ...Object.keys(mobile).length === 0 ? {} : { mobile }
13327
+ };
13328
+ };
13329
+ var rangesOf = (bucket, own) => {
13330
+ const layer = byRange(bucket);
13331
+ return {
13332
+ mobile: composed(own ? onTopOf(own.mobile, layer.mobile) : layer.mobile),
13333
+ tablet: composed(own ? onTopOf(own.tablet, layer.tablet) : layer.tablet),
13334
+ desktop: composed(own ? onTopOf(own.desktop, layer.desktop) : layer.desktop)
13335
+ };
13336
+ };
13337
+ /**
13338
+ * A `tw()` with colours of its own: `createTw({ colors: tokens(variables) })` makes `bg-surface` the space's token.
13339
+ *
13340
+ * `tw('flex items-center gap-2 px-5 py-2 rounded-full bg-slate-950/90 hover:scale-105 md:text-sm')` is the rules those
13341
+ * classes mean, written out when the space is — nothing of Tailwind runs in the page. Tailwind's breakpoints become
13342
+ * Plitzi's (`md:` tablet and desktop, `lg:` desktop, `max-md:` mobile, `max-lg:` tablet and mobile), its states the
13343
+ * class's states, `group-hover/<class>:` how it looks inside an ancestor wearing that class. A class Tailwind does not
13344
+ * have, or one with no exact equivalent here (`sm:`, `dark:`, `space-x-4`), is refused with what to write instead.
13345
+ */
13346
+ var createTw = ({ colors = {} } = {}) => (classes) => {
13347
+ const buckets = /* @__PURE__ */ new Map([["|", {
13348
+ target: {},
13349
+ bucket: {}
13350
+ }]]);
13351
+ for (const written of classes.split(/\s+/).filter(Boolean)) {
13352
+ const { breakpoint, target, utility } = parseClass(written);
13353
+ const resolved = resolveUtility(utility, colors);
13354
+ if (resolved === void 0) return refuse$1("tw-unknown-class", `"${written}" is not a Tailwind class tw() knows${didYouMean(utility, KNOWN_NAMES)} A colour of the space is \`createTw({ colors })\`; any CSS at all is \`[property:value]\`.`);
13355
+ if (resolved instanceof NoEquivalent) return refuse$1("tw-no-equivalent", `"${written}": ${resolved.reason}`);
13356
+ const key = targetKey(target);
13357
+ const entry = buckets.get(key) ?? {
13358
+ target,
13359
+ bucket: {}
13360
+ };
13361
+ buckets.set(key, entry);
13362
+ const declared = entry.bucket[breakpoint] ?? /* @__PURE__ */ new Map();
13363
+ entry.bucket[breakpoint] = declared;
13364
+ const weight = Object.keys(resolved).length;
13365
+ for (const [property, value] of Object.entries(resolved)) {
13366
+ const earlier = declared.get(property);
13367
+ if (earlier && earlier.weight === weight && earlier.value !== value) return refuse$1("tw-class-conflict", `"${earlier.by}" and "${written}" both set \`${property}\`, and neither is the narrower one — Tailwind would pick by its own order, not yours. Keep one.`);
13368
+ if (!earlier || weight <= earlier.weight) declared.set(property, {
13369
+ value,
13370
+ weight,
13371
+ by: written
13372
+ });
13373
+ }
13374
+ }
13375
+ const own = buckets.get("|")?.bucket ?? {};
13376
+ const ownRanges = byRange(own);
13377
+ const rules = { css: responsive(rangesOf(own)) };
13378
+ for (const [key, { target, bucket }] of buckets) {
13379
+ if (key === "|") continue;
13380
+ const css = responsive(rangesOf(bucket, ownRanges));
13381
+ if (target.state) rules.states = {
13382
+ ...rules.states,
13383
+ [target.state]: css
13384
+ };
13385
+ else if (target.ancestor) {
13386
+ const ancestors = { ...rules.ancestors };
13387
+ const { name, state } = target.ancestor;
13388
+ const ancestor = ancestors[name] ?? {};
13389
+ ancestors[name] = state ? {
13390
+ ...ancestor,
13391
+ states: {
13392
+ ...ancestor.states,
13393
+ [state]: css
13394
+ }
13395
+ } : {
13396
+ ...ancestor,
13397
+ css
13398
+ };
13399
+ rules.ancestors = ancestors;
13400
+ }
13401
+ }
13402
+ return rules;
13403
+ };
13404
+ createTw();
13405
+ new Set(Object.entries(g$4.content.defaultStyle.subTypes).filter(([, variant]) => "field" in variant.style).map(([subType]) => subType));
13406
+ /** A container with no tag of its own named: the row `list-row-not-li` makes an `<li>` without asking. */
13407
+ var isPlainContainer = (element) => element.definition.type === "container" && (element.attributes.subType === void 0 || element.attributes.subType === "div");
13408
+ new Set(STEP_TYPES);
13409
+ /** A step's params that name a path under `runtime.state`, by action. */
13410
+ var STATE_PATH_PARAMS = {
13411
+ setState: ["key"],
13412
+ toggleState: ["key"],
13413
+ appendState: ["key"],
13414
+ removeState: ["key"],
13415
+ toggleInState: ["key"],
13416
+ clearState: ["key"],
13417
+ moveState: ["from", "to"]
13418
+ };
13419
+ var t$6 = [
13420
+ "fade",
13421
+ "fade-up",
13422
+ "fade-down",
13423
+ "slide-left",
13424
+ "slide-right",
13425
+ "slide-up",
13426
+ "scale",
13427
+ "zoom-in",
13428
+ "zoom-out",
13429
+ "pop",
13430
+ "bounce-in",
13431
+ "tilt",
13432
+ "spin-in"
13433
+ ];
13434
+ var n$6 = [
13435
+ "load",
13436
+ "view",
13437
+ "scroll"
13438
+ ];
13439
+ var r$4 = [
13440
+ "float",
13441
+ "pulse",
13442
+ "spin",
13443
+ "sway",
13444
+ "bounce",
13445
+ "wobble",
13446
+ "orbit",
13447
+ "heartbeat",
13448
+ "drift"
13449
+ ];
13450
+ var o$6 = {
13451
+ fade: [{
13452
+ at: 0,
13453
+ opacity: 0
13454
+ }],
13455
+ "fade-up": [{
13456
+ at: 0,
13457
+ opacity: 0,
13458
+ translate: "0 24px"
13459
+ }],
13460
+ "fade-down": [{
13461
+ at: 0,
13462
+ opacity: 0,
13463
+ translate: "0 -24px"
13464
+ }],
13465
+ "slide-left": [{
13466
+ at: 0,
13467
+ opacity: 0,
13468
+ translate: "32px 0"
13469
+ }],
13470
+ "slide-right": [{
13471
+ at: 0,
13472
+ opacity: 0,
13473
+ translate: "-32px 0"
13474
+ }],
13475
+ "slide-up": [{
13476
+ at: 0,
13477
+ opacity: 0,
13478
+ translate: "0 72px"
13479
+ }],
13480
+ scale: [{
13481
+ at: 0,
13482
+ opacity: 0,
13483
+ scale: "0.94"
13484
+ }],
13485
+ "zoom-in": [{
13486
+ at: 0,
13487
+ opacity: 0,
13488
+ scale: "0.6"
13489
+ }],
13490
+ "zoom-out": [{
13491
+ at: 0,
13492
+ opacity: 0,
13493
+ scale: "1.25"
13494
+ }],
13495
+ pop: [
13496
+ {
13497
+ at: 0,
13498
+ opacity: 0,
13499
+ scale: "0.5"
13500
+ },
13501
+ {
13502
+ at: 55,
13503
+ opacity: 1,
13504
+ scale: "1.08"
13505
+ },
13506
+ {
13507
+ at: 78,
13508
+ scale: "0.97"
13509
+ }
13510
+ ],
13511
+ "bounce-in": [
13512
+ {
13513
+ at: 0,
13514
+ opacity: 0,
13515
+ translate: "0 -64px"
13516
+ },
13517
+ {
13518
+ at: 55,
13519
+ opacity: 1,
13520
+ translate: "0 10px"
13521
+ },
13522
+ {
13523
+ at: 75,
13524
+ translate: "0 -5px"
13525
+ },
13526
+ {
13527
+ at: 90,
13528
+ translate: "0 2px"
13529
+ }
13530
+ ],
13531
+ tilt: [{
13532
+ at: 0,
13533
+ opacity: 0,
13534
+ translate: "0 32px",
13535
+ rotate: "-6deg"
13536
+ }],
13537
+ "spin-in": [{
13538
+ at: 0,
13539
+ opacity: 0,
13540
+ scale: "0.4",
13541
+ rotate: "-180deg"
13542
+ }]
13543
+ };
13544
+ Object.fromEntries(t$6.map((e) => {
13545
+ let [{ at: t, ...n }] = o$6[e];
13546
+ return [e, n];
13547
+ }));
13548
+ var c$4 = (e, t) => {
13549
+ let n = t * 2 * Math.PI, r = Math.round(Math.sin(n) * e * 10) / 10, i = Math.round(-Math.cos(n) * e * 10) / 10;
13550
+ return `translate(${String(r)}px, ${String(i)}px)`;
13718
13551
  };
13552
+ Array.from({ length: 9 }, (e, t) => ({
13553
+ at: t * 100 / 8,
13554
+ transform: c$4(6, t / 8)
13555
+ }));
13556
+ [
13557
+ "[data-motion-loop] { animation-play-state: running, running !important; }",
13558
+ "[data-motion-on=\"view\"][data-motion-enter], [data-motion-on=\"view\"][data-motion-stagger] > * { animation-play-state: running, running !important; }",
13559
+ "[data-motion-on=\"scroll\"][data-motion-enter], [data-motion-on=\"scroll\"][data-motion-stagger] > * { animation-timeline: auto, auto !important; }"
13560
+ ].join("\n");
13561
+ Object.entries(AUTHORING_CODES).filter(([, entry]) => entry.kind === "suggested").map(([code]) => code);
13562
+ var URL_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
13563
+ /** A page target that is really a URL: the one reading of `mailto:`/`https:` a page-mode link can never mean. */
13564
+ var isUrlTarget = (target) => typeof target === "string" && !e$7(target) && URL_SCHEME.test(target);
13565
+ var isRoot = (element) => !element.definition.parentId;
13566
+ var stepsOf$1 = (element) => Object.values(element.definition.interactions ?? {});
13719
13567
  /**
13720
- * Desktop-first, as Plitzi writes it: the desktop rules apply everywhere, and tablet and mobile say only where they
13721
- * differ — a property the desktop sets and a narrower range does not is `revert`, which is what "no rule here" is.
13568
+ * A key the element or step does not take, renamed to the one it was a typo of — when that one is free — or dropped:
13569
+ * it was never read, so dropping it changes nothing on screen.
13722
13570
  */
13723
- var responsive = (ranges) => {
13724
- const { desktop } = ranges;
13725
- const differences = (range) => {
13726
- const changed = Object.fromEntries(Object.entries(range).filter(([property, value]) => desktop[property] !== value));
13727
- for (const property of Object.keys(desktop)) if (!Object.hasOwn(range, property)) changed[property] = "revert";
13728
- return changed;
13729
- };
13730
- const tablet = differences(ranges.tablet);
13731
- const mobile = differences(ranges.mobile);
13732
- if (Object.keys(tablet).length === 0 && Object.keys(mobile).length === 0) return desktop;
13733
- return {
13734
- ...Object.keys(desktop).length === 0 ? {} : { desktop },
13735
- ...Object.keys(tablet).length === 0 ? {} : { tablet },
13736
- ...Object.keys(mobile).length === 0 ? {} : { mobile }
13737
- };
13571
+ var settleUnknownKeys = (record, known, what, on, report, defaults = {}) => {
13572
+ for (const key of Object.keys(record)) {
13573
+ if (known.includes(key)) continue;
13574
+ const wrapped = record[key];
13575
+ if (e$33(wrapped) && Object.keys(wrapped).length > 0 && Object.keys(wrapped).every((inner) => known.includes(inner))) {
13576
+ const inners = Object.keys(wrapped);
13577
+ if (inners.every((inner) => record[inner] === void 0 || record[inner] === defaults[inner])) {
13578
+ Object.assign(record, wrapped);
13579
+ Reflect.deleteProperty(record, key);
13580
+ report(`Moved ${inners.join(", ")} out of the ${what} "${key}", up to where ${inners.length === 1 ? "it is" : "they are"} read.`, {
13581
+ on,
13582
+ op: "unwrap",
13583
+ key
13584
+ });
13585
+ }
13586
+ continue;
13587
+ }
13588
+ const meant = closest$1(key, known);
13589
+ if (meant !== void 0 && record[meant] === void 0) {
13590
+ record[meant] = record[key];
13591
+ report(`Renamed the ${what} "${key}" to "${meant}".`, {
13592
+ on,
13593
+ op: "rename",
13594
+ key,
13595
+ to: meant
13596
+ });
13597
+ } else report(`Removed the ${what} "${key}", which nothing reads.`, {
13598
+ on,
13599
+ op: "remove",
13600
+ key
13601
+ });
13602
+ Reflect.deleteProperty(record, key);
13603
+ }
13738
13604
  };
13739
- var rangesOf = (bucket, own) => {
13740
- const layer = byRange(bucket);
13741
- return {
13742
- mobile: composed(own ? onTopOf(own.mobile, layer.mobile) : layer.mobile),
13743
- tablet: composed(own ? onTopOf(own.tablet, layer.tablet) : layer.tablet),
13744
- desktop: composed(own ? onTopOf(own.desktop, layer.desktop) : layer.desktop)
13745
- };
13605
+ /** A flag written as the words `'true'`/`'false'`, stored as the boolean it is read as. */
13606
+ var asFlag = (value) => value === "true" ? true : value === "false" ? false : void 0;
13607
+ var stepSpec = (catalogs, step) => {
13608
+ const vocabulary = catalogs.vocabulary;
13609
+ const table = step.type === "globalCallback" ? vocabulary?.globalCallbacks : step.type === "utility" ? vocabulary?.utilities : step.type === "callback" ? vocabulary?.sharedCallbacks : void 0;
13610
+ return table && Object.hasOwn(table, step.action) ? table[step.action] : void 0;
13746
13611
  };
13747
- /**
13748
- * A `tw()` with colours of its own: `createTw({ colors: tokens(variables) })` makes `bg-surface` the space's token.
13749
- *
13750
- * `tw('flex items-center gap-2 px-5 py-2 rounded-full bg-slate-950/90 hover:scale-105 md:text-sm')` is the rules those
13751
- * classes mean, written out when the space is — nothing of Tailwind runs in the page. Tailwind's breakpoints become
13752
- * Plitzi's (`md:` tablet and desktop, `lg:` desktop, `max-md:` mobile, `max-lg:` tablet and mobile), its states the
13753
- * class's states, `group-hover/<class>:` how it looks inside an ancestor wearing that class. A class Tailwind does not
13754
- * have, or one with no exact equivalent here (`sm:`, `dark:`, `space-x-4`), is refused with what to write instead.
13755
- */
13756
- var createTw = ({ colors = {} } = {}) => (classes) => {
13757
- const buckets = /* @__PURE__ */ new Map([["|", {
13758
- target: {},
13759
- bucket: {}
13760
- }]]);
13761
- for (const written of classes.split(/\s+/).filter(Boolean)) {
13762
- const { breakpoint, target, utility } = parseClass(written);
13763
- const resolved = resolveUtility(utility, colors);
13764
- if (resolved === void 0) return refuse$1("tw-unknown-class", `"${written}" is not a Tailwind class tw() knows${didYouMean(utility, KNOWN_NAMES)} A colour of the space is \`createTw({ colors })\`; any CSS at all is \`[property:value]\`.`);
13765
- if (resolved instanceof NoEquivalent) return refuse$1("tw-no-equivalent", `"${written}": ${resolved.reason}`);
13766
- const key = targetKey(target);
13767
- const entry = buckets.get(key) ?? {
13768
- target,
13769
- bucket: {}
13770
- };
13771
- buckets.set(key, entry);
13772
- const declared = entry.bucket[breakpoint] ?? /* @__PURE__ */ new Map();
13773
- entry.bucket[breakpoint] = declared;
13774
- const weight = Object.keys(resolved).length;
13775
- for (const [property, value] of Object.entries(resolved)) {
13776
- const earlier = declared.get(property);
13777
- if (earlier && earlier.weight === weight && earlier.value !== value) return refuse$1("tw-class-conflict", `"${earlier.by}" and "${written}" both set \`${property}\`, and neither is the narrower one — Tailwind would pick by its own order, not yours. Keep one.`);
13778
- if (!earlier || weight <= earlier.weight) declared.set(property, {
13779
- value,
13780
- weight,
13781
- by: written
13612
+ new Set(Object.keys({
13613
+ "page-target-url": (element, _ctx, report) => {
13614
+ const { href, mode } = element.attributes;
13615
+ if (element.definition.type === "link" && (mode ?? "page") === "page" && isUrlTarget(href)) {
13616
+ element.attributes.mode = "external";
13617
+ report(`The link to "${href}" now opens it as an external URL.`, {
13618
+ on: "attribute",
13619
+ op: "set",
13620
+ key: "mode",
13621
+ value: "external"
13622
+ });
13623
+ }
13624
+ for (const step of stepsOf$1(element)) if (step.action === "navigate" && step.params.urlType === "page" && isUrlTarget(step.params.url)) {
13625
+ step.params.urlType = "external";
13626
+ report(`Step "${step.id}" now navigates to "${step.params.url}" as an external URL.`, {
13627
+ on: { step: step.id },
13628
+ op: "set",
13629
+ key: "urlType",
13630
+ value: "external"
13631
+ });
13632
+ }
13633
+ },
13634
+ "unknown-attribute": (element, ctx, report) => {
13635
+ const names = ctx.attributeNamesFor(element);
13636
+ if (!isRoot(element) && names) settleUnknownKeys(element.attributes, names, "attribute", "attribute", report, ctx.defaultsFor(element));
13637
+ },
13638
+ "attribute-kind": (element, ctx, report) => {
13639
+ const defaults = ctx.catalogs.defaultAttributes?.[element.definition.type] ?? {};
13640
+ for (const [name, value] of Object.entries(element.attributes)) {
13641
+ const flag = asFlag(value);
13642
+ if (typeof defaults[name] === "boolean" && flag !== void 0) {
13643
+ element.attributes[name] = flag;
13644
+ report(`\`${name}\` is now the boolean ${String(flag)}, not the text "${String(value)}".`, {
13645
+ on: "attribute",
13646
+ op: "set",
13647
+ key: name,
13648
+ value: flag
13649
+ });
13650
+ }
13651
+ }
13652
+ },
13653
+ "step-params": (element, ctx, report) => {
13654
+ for (const step of stepsOf$1(element)) {
13655
+ const spec = stepSpec(ctx.catalogs, step);
13656
+ if (!spec?.params) continue;
13657
+ const params = spec.params;
13658
+ if (spec.strictParams) settleUnknownKeys(step.params, Object.keys(params), `param of step "${step.id}"`, { step: step.id }, report);
13659
+ for (const [key, value] of Object.entries(step.params)) {
13660
+ const flag = asFlag(value);
13661
+ if (Object.hasOwn(params, key) && params[key].type === "boolean" && flag !== void 0) {
13662
+ step.params[key] = flag;
13663
+ report(`Step "${step.id}": \`${key}\` is now the boolean ${String(flag)}.`, {
13664
+ on: { step: step.id },
13665
+ op: "set",
13666
+ key,
13667
+ value: flag
13668
+ });
13669
+ }
13670
+ }
13671
+ }
13672
+ },
13673
+ "global-callback-module": (element, ctx, report) => {
13674
+ for (const step of stepsOf$1(element)) {
13675
+ const declared = step.type === "globalCallback" && ctx.catalogs.vocabulary ? ctx.catalogs.vocabulary.globalCallbacks[step.action] : void 0;
13676
+ if (declared && step.elementId !== declared.source) {
13677
+ step.elementId = declared.source;
13678
+ report(`Step "${step.id}" now runs "${step.action}" on "${declared.source}", the module that registers it.`, {
13679
+ on: { step: step.id },
13680
+ op: "set",
13681
+ key: "on",
13682
+ value: declared.source
13683
+ });
13684
+ }
13685
+ }
13686
+ },
13687
+ "utility-module": (element, ctx, report) => {
13688
+ for (const step of stepsOf$1(element)) {
13689
+ const known = ctx.catalogs.vocabulary ? Object.hasOwn(ctx.catalogs.vocabulary.utilities, step.action) : false;
13690
+ if (step.type === "utility" && known && step.elementId !== null) {
13691
+ step.elementId = null;
13692
+ report(`Step "${step.id}" runs the utility "${step.action}" on no element: a utility takes none.`, {
13693
+ on: { step: step.id },
13694
+ op: "remove",
13695
+ key: "on"
13696
+ });
13697
+ }
13698
+ }
13699
+ },
13700
+ "visibility-as-attribute": (element, _ctx, report) => {
13701
+ const bindings = element.definition.bindings;
13702
+ const moved = bindings?.attributes?.filter((binding) => binding.to === "visibility") ?? [];
13703
+ if (!bindings || moved.length === 0) return;
13704
+ bindings.attributes = bindings.attributes?.filter((binding) => binding.to !== "visibility");
13705
+ bindings.initialState = [...bindings.initialState ?? [], ...moved];
13706
+ report("The visibility binding now sets the visibility, rather than an attribute nothing reads.");
13707
+ },
13708
+ "binding-target-unknown": (element, ctx, report) => {
13709
+ const names = ctx.attributeNamesFor(element);
13710
+ const bindings = element.definition.bindings;
13711
+ if (!names || !bindings?.attributes) return;
13712
+ const reads = (binding) => binding.to === "visibility" || binding.to === "className" || names.includes(binding.to);
13713
+ for (const binding of bindings.attributes.filter((candidate) => !reads(candidate))) report(`Removed the binding onto "${binding.to}", which a "${element.definition.type}" never reads.`, {
13714
+ on: "binding",
13715
+ op: "remove",
13716
+ key: binding.to
13717
+ });
13718
+ bindings.attributes = bindings.attributes.filter(reads);
13719
+ },
13720
+ "unknown-transformer": (element, ctx, report) => {
13721
+ const catalog = ctx.catalogs.transformers;
13722
+ if (!catalog) return;
13723
+ for (const binding of Object.values(element.definition.bindings ?? {}).flat()) for (const transformer of binding.transformers ?? []) {
13724
+ const meant = Object.hasOwn(catalog, transformer.action) ? void 0 : closest$1(transformer.action, Object.keys(catalog));
13725
+ if (meant !== void 0) {
13726
+ report(`The binding of "${binding.to}" now runs "${meant}" (was "${transformer.action}").`, {
13727
+ on: "binding",
13728
+ op: "replace",
13729
+ key: transformer.action,
13730
+ to: meant
13731
+ });
13732
+ transformer.action = meant;
13733
+ }
13734
+ }
13735
+ },
13736
+ "list-items-ignored": (element, _ctx, report) => {
13737
+ const { attributes } = element;
13738
+ const bound = Object.values(element.definition.bindings ?? {}).flat().some((binding) => binding.to === "items");
13739
+ const written = Array.isArray(attributes.items) && attributes.items.length > 0;
13740
+ if (element.definition.type === "list" && attributes.source !== "controlled" && (bound || written)) {
13741
+ attributes.source = "controlled";
13742
+ report("The list now reads its items (`source: controlled`), one row per item.", {
13743
+ on: "attribute",
13744
+ op: "set",
13745
+ key: "source",
13746
+ value: "controlled"
13782
13747
  });
13783
13748
  }
13784
- }
13785
- const own = buckets.get("|")?.bucket ?? {};
13786
- const ownRanges = byRange(own);
13787
- const rules = { css: responsive(rangesOf(own)) };
13788
- for (const [key, { target, bucket }] of buckets) {
13789
- if (key === "|") continue;
13790
- const css = responsive(rangesOf(bucket, ownRanges));
13791
- if (target.state) rules.states = {
13792
- ...rules.states,
13793
- [target.state]: css
13794
- };
13795
- else if (target.ancestor) {
13796
- const ancestors = { ...rules.ancestors };
13797
- const { name, state } = target.ancestor;
13798
- const ancestor = ancestors[name] ?? {};
13799
- ancestors[name] = state ? {
13800
- ...ancestor,
13801
- states: {
13802
- ...ancestor.states,
13803
- [state]: css
13804
- }
13805
- } : {
13806
- ...ancestor,
13807
- css
13749
+ },
13750
+ "list-row-not-li": (element, ctx, report) => {
13751
+ const { parentId } = element.definition;
13752
+ const list = parentId ? ctx.element(parentId) : void 0;
13753
+ if (list?.definition.type === "list" && list.attributes.source === "controlled" && isPlainContainer(element)) {
13754
+ element.attributes.subType = "li";
13755
+ report("The row is now an `<li>` (`subType: li`), an item of the list it renders in.", {
13756
+ on: "attribute",
13757
+ op: "set",
13758
+ key: "subType",
13759
+ value: "li"
13760
+ });
13761
+ }
13762
+ },
13763
+ "overlay-starts-open": (element, _ctx, report) => {
13764
+ const { type, bindings } = element.definition;
13765
+ const visibilityBound = (bindings?.initialState ?? []).some((binding) => binding.to === "visibility");
13766
+ if ((type === "modalContainer" || type === "dialogContainer") && element.definition.initialState?.visibility !== false && !visibilityBound) {
13767
+ element.definition.initialState = {
13768
+ ...element.definition.initialState,
13769
+ visibility: false
13808
13770
  };
13809
- rules.ancestors = ancestors;
13771
+ report(`The ${type} now starts closed, to be opened from a flow.`, {
13772
+ on: "field",
13773
+ op: "set",
13774
+ key: "visible",
13775
+ value: false
13776
+ });
13777
+ }
13778
+ },
13779
+ "state-key-has-runtime-prefix": (element, _ctx, report) => {
13780
+ for (const step of stepsOf$1(element)) {
13781
+ if (step.type !== "globalCallback" || step.elementId !== "state") continue;
13782
+ for (const param of STATE_PATH_PARAMS[step.action] ?? []) {
13783
+ const value = step.params[param];
13784
+ if (typeof value === "string" && /^(?:runtime\.)?state\./.test(value)) {
13785
+ const next = value.replace(/^(?:runtime\.)?state\./, "");
13786
+ step.params[param] = next;
13787
+ report(`Step "${step.id}": \`${param}\` is now "${next}" (was "${value}").`, {
13788
+ on: { step: step.id },
13789
+ op: "set",
13790
+ key: param,
13791
+ value: next
13792
+ });
13793
+ }
13794
+ }
13810
13795
  }
13811
13796
  }
13812
- return rules;
13813
- };
13814
- createTw();
13797
+ }));
13798
+ var e$4 = ".Toastify__toast-container.plitzi-sdk-toasts";
13799
+ var t$4 = [
13800
+ {
13801
+ selector: e$4,
13802
+ fields: {
13803
+ background: ["--toastify-color-light", "--toastify-color-dark"],
13804
+ text: ["--toastify-text-color-light", "--toastify-text-color-dark"],
13805
+ success: ["--toastify-color-success"],
13806
+ danger: ["--toastify-color-error"],
13807
+ warning: ["--toastify-color-warning"],
13808
+ info: ["--toastify-color-info"],
13809
+ radius: ["--toastify-toast-bd-radius"],
13810
+ font: ["--toastify-font-family"],
13811
+ shadow: ["--toastify-toast-shadow"],
13812
+ padding: ["--toastify-toast-padding"],
13813
+ minHeight: ["--toastify-toast-min-height"]
13814
+ }
13815
+ },
13816
+ {
13817
+ selector: `:where(${e$4} .Toastify__toast)`,
13818
+ fields: {
13819
+ fontSize: ["font-size"],
13820
+ fontWeight: ["font-weight"],
13821
+ lineHeight: ["line-height"],
13822
+ border: ["border"]
13823
+ }
13824
+ },
13825
+ {
13826
+ selector: `${e$4} .Toastify__toast-icon`,
13827
+ fields: {
13828
+ iconSize: ["width"],
13829
+ iconGap: ["margin-inline-end"]
13830
+ }
13831
+ },
13832
+ {
13833
+ selector: `${e$4} .Toastify__close-button`,
13834
+ fields: { closeColor: ["color"] }
13835
+ },
13836
+ {
13837
+ selector: `${e$4} .Toastify__close-button:not(:hover, :focus)`,
13838
+ fields: { closeOpacity: ["opacity"] }
13839
+ },
13840
+ {
13841
+ selector: `${e$4} .Toastify__progress-bar--wrp`,
13842
+ fields: { progressHeight: ["height"] }
13843
+ }
13844
+ ];
13845
+ var n$4 = t$4.flatMap((e) => Object.keys(e.fields));
13846
+ var r$2$1 = (e) => n$4.includes(e);
13847
+ n$4.filter(r$2$1);
13848
+ var c$2$1 = (e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13849
+ t$4.map((e) => {
13850
+ let t = new Map(Object.entries(e.fields).flatMap(([e, t]) => r$2$1(e) ? t.map((t) => [t, e]) : []));
13851
+ return {
13852
+ pattern: RegExp(`(?:^|\\n)${c$2$1(e.selector)}\\s*\\{([^}]*)\\}`, "g"),
13853
+ fieldOf: (e) => t.get(e)
13854
+ };
13855
+ });
13856
+ Object.fromEntries(e$30.map((e) => [e, t$26(e)]));
13815
13857
  [...new Set(STYLE_STATES.filter((state) => state !== "hidden"))].filter((state) => u$7(state) !== `:${state}`);
13816
13858
  new Set("break case catch class const continue debugger default delete do else enum export extends false finally for function if import in instanceof new null return super switch this throw true try typeof var void while with yield let static implements interface package private protected public await element styles".split(" "));
13817
13859
  new Set(STYLE_STATES);
@@ -15275,7 +15317,7 @@ var pluginDeclarations = async (folder) => {
15275
15317
  }));
15276
15318
  };
15277
15319
  /** The manifest of a plugin run as it was built, read; nothing when it cannot be — the layout check says why first. */
15278
- var manifestOf$1 = (file) => {
15320
+ var manifestOf$2 = (file) => {
15279
15321
  try {
15280
15322
  const manifest = JSON.parse(readFileSync(file, "utf-8"));
15281
15323
  return e$33(manifest) ? manifest : {};
@@ -15288,7 +15330,7 @@ var manifestOf$1 = (file) => {
15288
15330
  * space): each folder's type and every element its manifest provides (`pluginSchema`). None in most projects.
15289
15331
  */
15290
15332
  var builtPluginTypes = (folder) => existsSync(folder) ? readdirSync(folder, { withFileTypes: true }).filter((entry) => entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name)).flatMap((entry) => {
15291
- const { pluginSchema } = manifestOf$1(path.join(folder, entry.name, d$1));
15333
+ const { pluginSchema } = manifestOf$2(path.join(folder, entry.name, d$1));
15292
15334
  return [entry.name, ...e$33(pluginSchema) ? Object.keys(pluginSchema) : []];
15293
15335
  }) : [];
15294
15336
  /**
@@ -18725,7 +18767,7 @@ var momentChanges = (before, after, state) => {
18725
18767
  ...stateChanges
18726
18768
  ];
18727
18769
  };
18728
- var selectorOf$1 = (authored, id) => authored && Object.hasOwn(authored.handles.elements, id) ? authored.handles.elements[id].selector : `[data-plitzi-el="${id}"]`;
18770
+ var selectorOf = (authored, id) => authored && Object.hasOwn(authored.handles.elements, id) ? authored.handles.elements[id].selector : `[data-plitzi-el="${id}"]`;
18729
18771
  var FIELDS = "input, textarea, select";
18730
18772
  /** The elements of the space the fields inside a node belong to: the nearest element around each. */
18731
18773
  var fieldOwnersInPage = (node, selector) => Array.from(node.querySelectorAll(selector), (field) => field.closest("[data-plitzi-el]")?.getAttribute("data-plitzi-el")).map((owner) => owner ?? "?");
@@ -18735,7 +18777,7 @@ var fieldOwnersInPage = (node, selector) => Array.from(node.querySelectorAll(sel
18735
18777
  * it is another's; or what could not be filled, and why.
18736
18778
  */
18737
18779
  var fillField = async (page, authored, { element, value }) => {
18738
- const node = page.locator(selectorOf$1(authored, element)).first();
18780
+ const node = page.locator(selectorOf(authored, element)).first();
18739
18781
  if (await node.count() === 0) return { problem: `no element ${element} is on this page to fill` };
18740
18782
  const own = await node.evaluate((each, selector) => each.matches(selector), FIELDS);
18741
18783
  const owners = own ? [element] : await node.evaluate(fieldOwnersInPage, FIELDS);
@@ -18760,7 +18802,7 @@ var fillField = async (page, authored, { element, value }) => {
18760
18802
  * nothing is said as such: a button whose flow does nothing looks the same as one that works until somebody clicks.
18761
18803
  */
18762
18804
  var clickEffects = async (page, authored, { element: id, fill }) => {
18763
- const target = page.locator(selectorOf$1(authored, id)).first();
18805
+ const target = page.locator(selectorOf(authored, id)).first();
18764
18806
  if (await target.count() === 0) return {
18765
18807
  element: id,
18766
18808
  problem: `no element ${id} is on this page`,
@@ -18889,12 +18931,12 @@ var lateElements = (schema, served, hydrated, width) => {
18889
18931
  browserOnly
18890
18932
  };
18891
18933
  };
18892
- var checkAt = async (browser, origin, pathname, width, scheme, authored, asked, ssr, account, click) => {
18934
+ var checkAt = async (browser, origin, pathname, width, view, authored, asked, ssr, account, click) => {
18893
18935
  const handles = authored?.handles;
18894
18936
  const page = await openProjectPage(browser, origin, {
18895
18937
  width,
18896
18938
  height: 900,
18897
- ...scheme ? { scheme } : {}
18939
+ ...view
18898
18940
  });
18899
18941
  /** A report of a page that could not be looked at — nothing answered, no sign-in, sent elsewhere. */
18900
18942
  const unchecked = (issue) => ({
@@ -19125,6 +19167,7 @@ var check = async (routes, options) => {
19125
19167
  asked,
19126
19168
  ssr: Boolean(options.ssr),
19127
19169
  ...options.scheme ? { scheme: options.scheme } : {},
19170
+ presence: options.presence === true,
19128
19171
  ...account ? { account } : {},
19129
19172
  ...options.click ? { click: {
19130
19173
  element: options.click,
@@ -19154,7 +19197,10 @@ var checkRoutes = async (project, routes, options) => {
19154
19197
  try {
19155
19198
  const reports = [];
19156
19199
  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);
19200
+ const report = await checkAt(browser, where.origin, route, width, {
19201
+ ...options.scheme ? { scheme: options.scheme } : {},
19202
+ presence: options.presence === true
19203
+ }, authored, options.asked, options.ssr, options.account, options.click);
19158
19204
  reports.push({
19159
19205
  ...report,
19160
19206
  ...options.scheme ? { scheme: options.scheme } : {}
@@ -19397,16 +19443,6 @@ var projectRunner = async (root) => {
19397
19443
  } catch {}
19398
19444
  fail("plitzi functions dev runs them with this project’s own @plitzi/sdk-server, and the isolates it needs:\n npm install --save-dev @plitzi/sdk-server isolated-vm core-js");
19399
19445
  };
19400
- /** `PLITZI_FUNCTIONS_CREDENTIALS`, a JSON object of credential id → its keys: what `ctx.fetch` may name locally. */
19401
- var localCredentials = () => {
19402
- const raw = process.env.PLITZI_FUNCTIONS_CREDENTIALS;
19403
- try {
19404
- const value = raw ? JSON.parse(raw) : {};
19405
- return e$3(value) ? Object.fromEntries(Object.entries(value).flatMap(([id, keys]) => e$3(keys) ? [[id, Object.fromEntries(Object.entries(keys).filter((pair) => typeof pair[1] === "string"))]] : [])) : {};
19406
- } catch {
19407
- return {};
19408
- }
19409
- };
19410
19446
  var NO_NODE_SNAPSHOT = "--no-node-snapshot";
19411
19447
  /**
19412
19448
  * The isolates `dev` runs need Node's startup snapshot off, and a process cannot turn it off once started: `dev` runs
@@ -19436,10 +19472,7 @@ var devFunction = async (task, options) => {
19436
19472
  const root = params && await rootOf();
19437
19473
  const runner = root ? await projectRunner(root) : void 0;
19438
19474
  if (!root || !runner) return;
19439
- const local = runner.createLocalFunctions({
19440
- credentials: localCredentials(),
19441
- dataDir: path.join(root, m)
19442
- });
19475
+ const local = runner.createLocalFunctions({ dataDir: path.join(root, m) });
19443
19476
  const run = async () => {
19444
19477
  const loaded = await local.load(await readFunctionsFiles(root));
19445
19478
  if (!loaded.ok) {
@@ -21204,6 +21237,125 @@ var fix = async (options) => {
21204
21237
  ].join("\n"));
21205
21238
  };
21206
21239
  //#endregion
21240
+ //#region src/commands/functionsAdd.ts
21241
+ /** Where a package added to the functions is kept: their own code from then on, copied in. */
21242
+ var VENDOR_DIR = "vendor";
21243
+ /** What a space's functions may weigh built, whole — a package added is part of it (`@plitzi/sdk-server`'s cap). */
21244
+ var MAX_FUNCTIONS_BYTES = 1e6;
21245
+ /** A package's name, as npm writes one: `ical.js`, `@scope/name`. */
21246
+ var PACKAGE_NAME = /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i;
21247
+ /** Globals of Node a package may read and a runner does not have: said, since a bundle cannot tell if they run. */
21248
+ var NODE_GLOBALS = /\b(process\.(env|nextTick|platform|versions?)|Buffer\.(from|alloc|isBuffer)|__dirname|__filename)\b/;
21249
+ /** The file a package is kept in, under `src/functions/vendor/`: its name, with its scope folded in. */
21250
+ var vendorFileOf = (name) => `${name.replace(/^@/, "").replace("/", "__")}.js`;
21251
+ /** The package's own `package.json`, found from where it resolves: its version and its licence for the header. */
21252
+ var manifestOf$1 = async (entry, name) => {
21253
+ for (let dir = path.dirname(entry); dir !== path.dirname(dir); dir = path.dirname(dir)) try {
21254
+ const parsed = JSON.parse(await fs.readFile(path.join(dir, "package.json"), "utf8"));
21255
+ if (e$3(parsed) && parsed.name === name) return {
21256
+ version: typeof parsed.version === "string" ? parsed.version : "?",
21257
+ license: typeof parsed.license === "string" ? parsed.license : "no licence stated"
21258
+ };
21259
+ } catch {}
21260
+ return {
21261
+ version: "?",
21262
+ license: "no licence stated"
21263
+ };
21264
+ };
21265
+ /**
21266
+ * One package bundled for the functions: web APIs only, as they run — what reaches Node (`fs`, `node:crypto`) does
21267
+ * not resolve on a neutral platform, and is the reason it is refused. With what it exports, for its types.
21268
+ */
21269
+ var bundle = async (root, name, withDefault) => {
21270
+ try {
21271
+ const result = await build({
21272
+ stdin: {
21273
+ contents: `export * from '${name}';${withDefault ? `\nexport { default } from '${name}';` : ""}`,
21274
+ resolveDir: root,
21275
+ loader: "js"
21276
+ },
21277
+ bundle: true,
21278
+ write: false,
21279
+ metafile: true,
21280
+ format: "esm",
21281
+ platform: "neutral",
21282
+ mainFields: ["module", "main"],
21283
+ target: "es2022",
21284
+ minify: true,
21285
+ legalComments: "inline",
21286
+ logLevel: "silent"
21287
+ });
21288
+ const exports = Object.values(result.metafile.outputs).flatMap((output) => output.exports);
21289
+ return {
21290
+ code: result.outputFiles.map((output) => output.text).join(""),
21291
+ exports
21292
+ };
21293
+ } catch (error) {
21294
+ return { errors: e$3(error) && Array.isArray(error.errors) ? error.errors.flatMap((entry) => e$3(entry) && typeof entry.text === "string" ? [{ text: entry.text }] : []) : [{ text: error instanceof Error ? error.message : String(error) }] };
21295
+ }
21296
+ };
21297
+ /** Why esbuild could not bundle it, in the words that say what to do: a Node module is a package that cannot run here. */
21298
+ var refusalOf$1 = (name, errors) => {
21299
+ const node = errors.map((entry) => /Could not resolve "([^"]+)"/.exec(entry.text)?.[1]).find((module) => module !== void 0 && !module.startsWith("."));
21300
+ return node ? `${name} reaches "${node}", which the functions do not have: they run with web APIs only — fetch, crypto.subtle, URL — on any runner, so a package that needs Node cannot run there. Look for one written for the browser or the edge.` : `${name} could not be bundled: ${errors.map((entry) => entry.text).join("; ")}`;
21301
+ };
21302
+ /**
21303
+ * `plitzi functions add <package>`: a package the functions use, as a function carries its dependencies — bundled into
21304
+ * `src/functions/vendor/`, and from then on their own code: pushed and built with the rest, read by the builder, run by
21305
+ * the runner. Nothing is resolved anywhere else, so a space's functions stay its own files. Installed first, with the
21306
+ * project's package manager, so its version is the project's choice and its types reach the editor.
21307
+ */
21308
+ var addFunctionsPackage = async (name, options) => {
21309
+ if (!PACKAGE_NAME.test(name)) {
21310
+ fail(`"${name}" is not a package's name — ical.js, @scope/name.`);
21311
+ return;
21312
+ }
21313
+ const root = (await projectHere("whose functions these are"))?.root;
21314
+ if (!root) return;
21315
+ let entry;
21316
+ try {
21317
+ entry = createRequire(path.join(root, "package.json")).resolve(name);
21318
+ } catch {
21319
+ try {
21320
+ entry = createRequire(path.join(root, "package.json")).resolve(`${name}/package.json`);
21321
+ } catch {
21322
+ fail(`${name} is not installed in this project: install it first (npm install --save-dev ${name}), then add it.`);
21323
+ return;
21324
+ }
21325
+ }
21326
+ let built = await bundle(root, name, true);
21327
+ if ("errors" in built && built.errors.some((entry) => /No matching export .*"default"/.test(entry.text))) built = await bundle(root, name, false);
21328
+ if ("errors" in built) {
21329
+ fail(refusalOf$1(name, built.errors));
21330
+ return;
21331
+ }
21332
+ const bytes = Buffer.byteLength(built.code);
21333
+ if (bytes > MAX_FUNCTIONS_BYTES) {
21334
+ fail(`${name} is ${String(Math.round(bytes / 1024))} KB bundled, and a space's functions are at most ${String(MAX_FUNCTIONS_BYTES / 1e3)} KB built, all of it: look for a smaller one, or import less of it.`);
21335
+ return;
21336
+ }
21337
+ const { version, license } = await manifestOf$1(entry, name);
21338
+ const file = vendorFileOf(name);
21339
+ const relative = `./${VENDOR_DIR}/${file}`;
21340
+ const header = `// ${name}@${version} — ${license}. Added by \`plitzi functions add ${name}\`: the package bundled for the functions\n// (web APIs only), imported as '${relative}'. Not to be edited: run it again to update it.\n`;
21341
+ const types = `// The types of ${name}@${version}, for '${relative}' — the installed package's own.\n${built.exports.filter((exported) => exported !== "default").length > 0 ? `export * from '${name}';\n` : ""}${built.exports.includes("default") ? `export { default } from '${name}';\n` : ""}`;
21342
+ const written = {
21343
+ [path.join(VENDOR_DIR, file)]: `${header}${built.code}`,
21344
+ [path.join(VENDOR_DIR, file.replace(/\.js$/, ".d.ts"))]: types
21345
+ };
21346
+ const base = path.join(root, h$1);
21347
+ if (options.dryRun) {
21348
+ sayDryRun(`plitzi functions add ${name}`, [...(await filesWouldWrite(base, Object.keys(written))).map((line) => line.replace(/^(.) /, `$1 ${h$1}/`))]);
21349
+ return;
21350
+ }
21351
+ for (const [target, text] of Object.entries(written)) {
21352
+ await fs.mkdir(path.dirname(path.join(base, target)), { recursive: true });
21353
+ await fs.writeFile(path.join(base, target), text);
21354
+ }
21355
+ console.log(`Added ${name}@${version} (${String(Math.round(bytes / 1024))} KB) as ${h$1}/${VENDOR_DIR}/${file}: import it from a file of ${h$1}/ with '${relative}' — \`functions push\` saves it with the rest.`);
21356
+ if (NODE_GLOBALS.test(built.code)) console.warn(`${name} reads globals of Node (process, Buffer, __dirname) somewhere: the functions have none, so whatever reads them fails when it runs. Try what you use with \`plitzi functions dev\`.`);
21357
+ };
21358
+ //#endregion
21207
21359
  //#region src/commands/siteOwnership.ts
21208
21360
  var NOT_ASKED = (host) => `${host} is not served from this machine, so importing it takes proof that it is yours: its domain verified on one of your Plitzi spaces. That is asked of your Plitzi account, signing in — run it again with --account to ask. A site that is not yours is not imported.`;
21209
21361
  var LOOPBACK = /^(127\.|::1$|::ffff:127\.)/;
@@ -23658,12 +23810,13 @@ var parseSteps = (script, every) => {
23658
23810
  });
23659
23811
  break;
23660
23812
  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>` };
23813
+ const quoted = /^(["'])(.+?)\1\s+(.+)$/s.exec(rest.join(" "));
23814
+ const [target, text] = quoted ? [quoted[2], quoted[3]] : [rest[0], rest.slice(1).join(" ")];
23815
+ if (!target || text === "") return { problem: `${at}: type needs an element and the text — type <element> <text>, type ".panel textarea" hi` };
23663
23816
  steps.push({
23664
23817
  kind: "type",
23665
23818
  target,
23666
- text: unquoted(words.join(" "))
23819
+ text: unquoted(text)
23667
23820
  });
23668
23821
  break;
23669
23822
  }
@@ -23733,12 +23886,42 @@ var TEXTS_SHOWN = 12;
23733
23886
  var DRIFT_PX = 2;
23734
23887
  /** An arrival that waits for the scroll and has not had it: one waiting to be seen, or one tied to the scroll. */
23735
23888
  var WAITING_FOR_SCROLL = "[data-motion-on=\"view\"]:not([data-motion-seen]), [data-motion-on=\"scroll\"]";
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}"]`;
23738
- var framing = (view) => {
23739
- if (view.clip) return {
23889
+ var BARE = /^[\w-]+$/;
23890
+ /** A bare word: an element's name — letters, digits, `-` and `_`, as an id is written — or else a tag (`textarea`). */
23891
+ var namedBy = (target) => `[data-plitzi-el="${target}"]`;
23892
+ /** What a wait waits for: an element by name or, a bare word naming none, a tag — whichever shows first. */
23893
+ var awaited = (target) => BARE.test(target) ? `${namedBy(target)}, ${target}` : target;
23894
+ /**
23895
+ * A target found on the page: an element's name, a tag no element is named by (`textarea`), or a selector — CSS or
23896
+ * Playwright's own (`button:has-text("Orbit")`, `text=Saved`, `role=button[name="Publish"]`). With how many it finds
23897
+ * (none is one not on the page), or why it is no selector at all: Playwright throws on one it cannot read, and an
23898
+ * uncaught throw ended the command with a stack trace.
23899
+ */
23900
+ var targetOn = async (page, target) => {
23901
+ try {
23902
+ if (!BARE.test(target)) return {
23903
+ selector: target,
23904
+ found: await page.locator(target).first().count() > 0
23905
+ };
23906
+ if (await page.locator(namedBy(target)).first().count() > 0) return {
23907
+ selector: namedBy(target),
23908
+ found: true
23909
+ };
23910
+ return await page.locator(target).first().count() > 0 ? {
23911
+ selector: target,
23912
+ found: true
23913
+ } : {
23914
+ selector: namedBy(target),
23915
+ found: false
23916
+ };
23917
+ } catch (error) {
23918
+ return { problem: `${target} is neither an element's name nor a selector the page can read: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}` };
23919
+ }
23920
+ };
23921
+ var framing = (view, clip) => {
23922
+ if (clip) return {
23740
23923
  kind: "element",
23741
- selector: selectorOf(view.clip)
23924
+ selector: clip
23742
23925
  };
23743
23926
  return view.viewport || view.scrollTo ? { kind: "viewport" } : { kind: "page" };
23744
23927
  };
@@ -23763,20 +23946,37 @@ var pictureOf = async (browser, url, view, origin) => {
23763
23946
  if (notSignedIn) return { problem: `--as ${notSignedIn}.` };
23764
23947
  if (!(view.fromLoad ? await page.goto(url, { waitUntil: "domcontentloaded" }).catch(() => null) : await openPage(page, url))) return { problem: `Nothing answers at ${url}.${origin ? " Start the project first: npm start" : ""}` };
23765
23948
  const sentTo = origin ? sentElsewhere(page, new URL(url).pathname) : void 0;
23766
- if (view.waitFor) {
23767
- if (!await page.waitForSelector(selectorOf(view.waitFor), { timeout: 1e4 }).catch(() => null)) return { problem: `${view.waitFor} did not appear on ${url} within 10 s.` };
23949
+ for (const target of [
23950
+ view.waitFor,
23951
+ view.scrollTo,
23952
+ view.clip
23953
+ ]) {
23954
+ const read = target === void 0 ? void 0 : await targetOn(page, target);
23955
+ if (read && "problem" in read) return read;
23768
23956
  }
23769
- for (const target of [view.scrollTo, view.clip]) if (target && !await page.waitForSelector(selectorOf(target), { timeout: 1e4 }).catch(() => null)) return { problem: `${target} is not on ${url}.` };
23770
- if (view.scrollTo) await page.locator(selectorOf(view.scrollTo)).first().scrollIntoViewIfNeeded();
23957
+ if (view.waitFor) {
23958
+ if (!await page.waitForSelector(awaited(view.waitFor), { timeout: 1e4 }).catch(() => null)) return { problem: `${view.waitFor} did not appear on ${url} within 10 s.` };
23959
+ }
23960
+ /** The element a target names, once it is there: what it is scrolled to, or pictured alone. */
23961
+ const shown = {};
23962
+ for (const target of [view.scrollTo, view.clip]) {
23963
+ if (target === void 0) continue;
23964
+ const read = await page.waitForSelector(awaited(target), { timeout: 1e4 }).catch(() => null) ? await targetOn(page, target) : void 0;
23965
+ if (!read || "problem" in read || !read.found) return { problem: `${target} is not on ${url}.` };
23966
+ shown[target] = read.selector;
23967
+ }
23968
+ if (view.scrollTo) await page.locator(shown[view.scrollTo]).first().scrollIntoViewIfNeeded();
23969
+ const how = framing(view, view.clip === void 0 ? void 0 : shown[view.clip]);
23771
23970
  await withoutDevTools(page);
23772
23971
  const settled = view.still ? await page.evaluate((selector) => document.querySelectorAll(selector).length, WAITING_FOR_SCROLL) : 0;
23773
- const whole = framing(view).kind === "page";
23972
+ const whole = how.kind === "page";
23774
23973
  const pictures = whole ? await page.evaluate(loadImages, IMAGES_BUDGET_MS) : void 0;
23775
23974
  if (whole) await page.evaluate(unrollPage, void 0);
23776
23975
  if (view.still) await page.addStyleTag({ content: _ });
23777
23976
  return {
23778
23977
  page,
23779
- png: await capture(page, framing(view)),
23978
+ how,
23979
+ png: await capture(page, how),
23780
23980
  scheme: await paintedScheme(page, view.scheme ?? "light"),
23781
23981
  settled,
23782
23982
  unloaded: pictures?.missing ?? 0,
@@ -23894,8 +24094,10 @@ var clickThrough = async (taken, how, clicks, { frames, every }) => {
23894
24094
  }];
23895
24095
  const clicked = [];
23896
24096
  for (const target of clicks) {
23897
- const element = taken.page.locator(selectorOf(target)).first();
23898
- if (await element.count() === 0) return { problem: `${target} is not on the page: --click takes an element's name or a CSS selector.` };
24097
+ const read = await targetOn(taken.page, target);
24098
+ if ("problem" in read) return read;
24099
+ if (!read.found) return { problem: `${target} is not on the page: --click takes an element's name, a tag or a selector.` };
24100
+ const element = taken.page.locator(read.selector).first();
23899
24101
  const before = moments[moments.length - 1].png;
23900
24102
  try {
23901
24103
  await element.click({ timeout: 3e3 });
@@ -23959,13 +24161,19 @@ var playSteps = async (taken, how, steps) => {
23959
24161
  };
23960
24162
  for (const [index, step] of steps.entries()) try {
23961
24163
  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);
24164
+ const read = await targetOn(page, step.target);
24165
+ if ("problem" in read || !read.found) return { problem: `Step ${String(index + 1)}: ${"problem" in read ? read.problem : `${step.target} is not on the page — its name, a tag or a selector; one with spaces in quotes: type ".panel textarea" hola`}.` };
24166
+ await page.locator(read.selector).first().click({ timeout: 3e3 });
24167
+ if (step.kind === "type") {
24168
+ if (!await page.evaluate(() => {
24169
+ const focused = document.activeElement;
24170
+ return focused instanceof HTMLElement && (focused.isContentEditable || focused.matches("input, textarea, select"));
24171
+ }, void 0)) return { problem: `Step ${String(index + 1)}: clicking ${step.target} leaves no field focused, so the text would go to the page itself — name the field: type ".panel textarea" hola` };
24172
+ await page.keyboard.type(step.text);
24173
+ }
23966
24174
  } else if (step.kind === "press") await page.keyboard.press(step.key);
23967
24175
  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 });
24176
+ else if (step.kind === "wait-for") await page.waitForSelector(awaited(step.target), { timeout: 1e4 });
23969
24177
  else if (step.kind === "shot") await picture(step.label ?? `step ${String(index + 1)}`);
23970
24178
  else for (let frame = 1; frame <= step.count; frame += 1) {
23971
24179
  if (frame > 1) await page.waitForTimeout(step.every);
@@ -24052,16 +24260,17 @@ var shot = async (route, options) => {
24052
24260
  ...options.clip ? { clip: options.clip } : {},
24053
24261
  ...options.scrollTo ? { scrollTo: options.scrollTo } : {},
24054
24262
  ...options.viewport ? { viewport: true } : {},
24263
+ ...options.presence ? { presence: true } : {},
24055
24264
  ...account ? { account } : {},
24056
24265
  ...fromLoad ? { fromLoad } : {},
24057
24266
  still: !options.clip && !options.scrollTo && !options.viewport && (options.frames ?? 0) <= 1 && clicks.length === 0
24058
24267
  };
24059
- const how = framing(view);
24060
24268
  const taken = await pictureOf(browser, `${where.origin}${pathname}`, view, where.origin);
24061
24269
  if ("problem" in taken) {
24062
24270
  fail(taken.problem);
24063
24271
  return;
24064
24272
  }
24273
+ const { how } = taken;
24065
24274
  const name = pathname === "/" ? "home" : pathname.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "");
24066
24275
  const out = path.resolve(project.root, options.out ?? `tmp/shots/${name}-${String(view.width)}-${taken.scheme}.png`);
24067
24276
  const base = out.replace(/\.png$/, "");
@@ -27503,14 +27712,14 @@ program.command("login").description("Sign in, in your browser. The session is k
27503
27712
  program.command("logout").description("Sign out: the session is revoked on the platform and forgotten here").action(() => logout());
27504
27713
  program.command("whoami").description("Who the CLI is signed in as, and the space it works in").option("--json", "One object: { api, user, space }, for a tool or an agent").option(...API_OPTION).action((options) => whoami(options));
27505
27714
  spaceGroup.command("use").description("Choose the space to work in, in your browser. One at a time: it replaces the one chosen before").option(...API_OPTION).action((options) => space$2(options));
27506
- pageGroup.command("check").argument("[paths...]", "The pages: /about /writers, in one browser. The home page when left out").description("Whether a page of the running project is whole, in text: every element on screen, no broken image, no console error, no failed flow").addOption(widthsOption("The widths to check it at, separated by commas", [1440, 390])).addOption(schemeOption()).option("--state", "Also what the page holds: its state, and every source by name").option("--element <id>", "Also one element: what it reads, its own state, whether it is on screen").option("--ssr", "Also the HTML the server sent against the hydrated page: what a server provider sent late").option("--click <id>", "Click one element once the page settles, and say what it changed — or that nothing did").addOption(new Option("--fill <id=value>", "Fill a field before the click, as a visitor types in it — once per field; a box takes true or false").argParser(fill)).option("--as <username>", "Sign in first as this account, through the server’s /auth routes — the password from PLITZI_CHECK_PASSWORD, in .env").option("--json", "One object per page and width, for a tool or an agent").action((paths, options) => check(paths, options));
27715
+ pageGroup.command("check").argument("[paths...]", "The pages: /about /writers, in one browser. The home page when left out").description("Whether a page of the running project is whole, in text: every element on screen, no broken image, no console error, no failed flow").addOption(widthsOption("The widths to check it at, separated by commas", [1440, 390])).addOption(schemeOption()).option("--state", "Also what the page holds: its state, and every source by name").option("--element <id>", "Also one element: what it reads, its own state, whether it is on screen").option("--ssr", "Also the HTML the server sent against the hydrated page: what a server provider sent late").option("--click <id>", "Click one element once the page settles, and say what it changed — or that nothing did").addOption(new Option("--fill <id=value>", "Fill a field before the click, as a visitor types in it — once per field; a box takes true or false").argParser(fill)).option("--as <username>", "Sign in first as this account, through the server’s /auth routes — the password from PLITZI_CHECK_PASSWORD, in .env").option("--presence", "Take part in the page’s realtime channels as a visitor does — arrive, announce, send. Otherwise it only watches them").option("--json", "One object per page and width, for a tool or an agent").action((paths, options) => check(paths, options));
27507
27716
  spaceGroup.command("fix").description("What authoring would fix in the space, as edits to your source: shown as a diff; with --write written, formatted and checked").option("--write", "Write the edits — kept only when the space authors with each fix gone and no problem added").option("--json", "The plan as one object, for a tool or an agent").action((options) => fix(options));
27508
27717
  pageGroup.command("import").argument("<url>", "The page to start from: https://example.com/pricing").description("A page of a site you verified, as a place to start writing from: its tokens, the outline of its blocks per breakpoint, its lists as JSON — never its words").option("-o, --out <dir>", "Where it is written, inside the project", "src/imported").addOption(widthsOption("The widths it is measured at: the widest is desktop, then tablet and mobile", [
27509
27718
  1440,
27510
27719
  768,
27511
27720
  390
27512
27721
  ])).option("-f, --force", "Write over what --out already holds").option("--account", "A site not served from this machine: ask your Plitzi account whether one of your spaces verified its domain (signs in)").option("--json", "One object, for a tool or an agent").option(...API_OPTION).option(...DRY_RUN_OPTION).action((url, options) => importPage(url, options));
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));
27722
+ pageGroup.command("shot").argument("[path]", "The page: /about. The home page when left out").description("A picture of one page of the running project — and, asked, how it differs from another or what moves").addOption(new Option("--width <px>", "The viewport width").argParser(width).default(1280)).addOption(new Option("--height <px>", "The viewport height (the picture is the whole page unless --viewport, --scroll-to or --clip)").argParser(positiveInteger).default(800)).addOption(schemeOption()).option("-o, --out <file>", "Where the picture goes. tmp/shots/<page>-<width>-<scheme>.png by default").option("--compare <url>", "Another site: the same page there, side by side, and how much differs by section").addOption(new Option("--frames <n>", "Pictures taken one after another: which sections move (a marquee, an autoplay)").argParser(positiveInteger)).addOption(new Option("--every <ms>", "How far apart --frames are taken").argParser(positiveInteger).default(500)).addOption(new Option("--from <moment>", "When --frames start: once the page settles, or as soon as its HTML is in (load) — what plays as it arrives").choices([...SHOT_MOMENTS]).default("settled")).option("--wait-for <element>", "An element to wait for first: its name (data-plitzi-el) or a CSS selector").option("--clip <element>", "Only this element — its name or a CSS selector — scrolled to wherever it is").option("--scroll-to <element>", "The page scrolled until this element is in view; the picture is the screen there").option("--viewport", "Only what fits the viewport, not the whole page").option("--click <element...>", "Click these in order once the page settles — each its name or a CSS selector, inside a plugin too — picturing what each changed (--frames pictures --every ms apart after each)").option("--sheet", "Every picture taken — before, after each click, each frame — on one contact sheet").option("--steps <script>", "An interaction, step by step: \"click <el>; type <el> <text>; press <key>; wait <ms>; wait-for <el>; shot [label]; frames <n> [ms]\" — or @file with one per line").option("--reduced-motion", "As a visitor who asked for less motion").option("--as <username>", "Sign in first as this account, through the server’s /auth routes — the password from PLITZI_CHECK_PASSWORD, in .env").option("--presence", "Take part in the page’s realtime channels as a visitor does — arrive, announce, send. Otherwise it only watches them").option("--json", "One object, for a tool or an agent").action((path, options) => shot(path, options));
27514
27723
  program.command("verify").description("Whether the project is left passing: author, lints, types, format and every page, only the problems said").option("--no-pages", "Leave the pages out — said as such").option("--keep-going", "Run every check even after one fails — by default the first failure ends the run").option("--json", "One object, for a tool or an agent").action((options) => verify(options));
27515
27724
  elementGroup.command("where").argument("<query>", "An element’s id, a class it wears, or words it shows: hero-cta, navLink, \"Get started\"").description("Where the project’s code writes an element: the file, the line and the call itself").addOption(new Option("--by <reading>", "Read the query one way only, not the first of id, class and words that matches").choices(WHERE_READINGS)).option("--json", "One object, for a tool or an agent").action((query, options) => where(query, options));
27516
27725
  elementGroup.command("edit").argument("<id>", "The element, by its id: hero-cta").description("Change an element’s attributes in the call that writes it, then check the space still authors").option("--set <key=value...>", "An attribute to write: content=\"Start free\", required=true").option("--remove <key...>", "An attribute to take out").option("--every", "The call writes other elements too, and the change is for every one").option("--json", "One object, for a tool or an agent").action((id, options) => edit(id, options));
@@ -27528,6 +27737,7 @@ pluginGroup.command("upload").argument("[zip]", "The zip plitzi plugin pack buil
27528
27737
  var functions = program.command("functions").description(`The space’s own server code: ${h$1}/ in this project is a working copy of it`);
27529
27738
  functions.command("pull").description(`Write the space’s functions into ${h$1}/ — refused when that would overwrite what is not pushed`).option("-f, --force", "Overwrite what is not pushed, or a copy of another space").option(...API_OPTION).option(...DRY_RUN_OPTION).action((options) => pullFunctions(options));
27530
27739
  functions.command("push").description(`Save ${h$1}/ as the space’s draft: built and checked on the platform, refused if it moved on since`).option(...API_OPTION).option(...DRY_RUN_OPTION).action((options) => pushFunctions(options));
27740
+ functions.command("add").argument("<package>", "An npm package the project installed — ical.js, @scope/name").description(`Bundle a package into ${h$1}/vendor/ for the functions to import, as a function carries its dependencies — web APIs only`).option(...DRY_RUN_OPTION).action((name, options) => addFunctionsPackage(name, options));
27531
27741
  functions.command("try").argument("<task>", "The task, <namespace>.<action>").description("Run one task of the draft in the sandbox: its value, what it logged, and why it failed").option("--params <json>", "Its params, as a JSON object").option(...API_OPTION).action((task, options) => tryFunction(task, options));
27532
27742
  functions.command("dev").argument("<task>", "The task, <namespace>.<action>").description(`Run one task from ${h$1}/ on this machine, as the platform runs it — nothing reaches the space`).option("--params <json>", "Its params, as a JSON object").option("-w, --watch", `Run it again every time a file of ${h$1}/ is saved`).action((task, options) => devFunction(task, options));
27533
27743
  runtime.command("push").description("Pack this project’s runtime module and keep it as the space’s draft runtime").option("--entry <path>", "The module whose default export is defineRuntime(…)", "src/runtime/index.ts").option(...API_OPTION).option(...DRY_RUN_OPTION).action((options) => pushRuntime(options));