@plitzi/cli 0.38.13 → 0.38.14

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
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { InvalidArgumentError, Option, program } from "commander";
3
3
  import fs, { mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import https from "node:https";
4
5
  import { builtinModules, createRequire, isBuiltin } from "node:module";
5
6
  import path, { dirname, join, posix, relative, sep } from "node:path";
6
7
  import { fileURLToPath, pathToFileURL } from "node:url";
7
8
  import chalk from "chalk";
8
9
  import { execFile, spawn, spawnSync } from "node:child_process";
9
- import os from "node:os";
10
+ import os, { hostname, networkInterfaces } from "node:os";
10
11
  import { createHash, randomBytes } from "node:crypto";
11
12
  import { createServer } from "node:http";
12
13
  import tls from "node:tls";
@@ -19,16 +20,16 @@ import { lookup } from "node:dns/promises";
19
20
  import { unzipSync, zipSync } from "fflate";
20
21
  import { gzipSync } from "node:zlib";
21
22
  import { emitKeypressEvents } from "node:readline";
22
- var t$7 = `[data-plitzi-devtools] { display: none !important; }`;
23
+ var t$8 = `[data-plitzi-devtools] { display: none !important; }`;
23
24
  //#endregion
24
25
  //#region ../../packages/sdk-shared/dist/helpers/isRecord.mjs
25
- var e$5 = (e) => typeof e == "object" && !!e && !Array.isArray(e);
26
+ var e$6 = (e) => typeof e == "object" && !!e && !Array.isArray(e);
26
27
  //#endregion
27
28
  //#region ../../packages/sdk-shared/dist/realtime/topics.mjs
28
29
  var m$2 = "plitzi-observer";
29
30
  //#endregion
30
31
  //#region ../../packages/sdk-shared/dist/theme/themeCookie.mjs
31
- var t$5 = "theme";
32
+ var t$7 = "theme";
32
33
  //#endregion
33
34
  //#region ../../packages/sdk-shared/dist/project/paths.mjs
34
35
  var n$7 = "plitzi";
@@ -66,6 +67,13 @@ var x$3 = "tmp/dev-server.json";
66
67
  * CLI's all the same: `plitzi upgrade` keeps it current, and what a project changes goes in `src/config/serverOptions.ts`.
67
68
  */
68
69
  var MAIN_FILE = "src/main.ts";
70
+ /**
71
+ * The project's own notes for agents — how it is built, what must not be undone — which `CLAUDE.md` and `AGENTS.md`,
72
+ * both the CLI's, point at. The project's: `plitzi upgrade` never touches it.
73
+ */
74
+ var PROJECT_NOTES = "NOTES.md";
75
+ /** The compiler options the CLI keeps up, which the project's own `tsconfig.json` extends. */
76
+ var TSCONFIG_BASE = `${n$7}/tsconfig.base.json`;
69
77
  /** The space's server actions, a folder like the space: `index.ts` lists them for the server, one action a file. */
70
78
  var ACTIONS_DIR = "src/actions";
71
79
  var ACTIONS_ENTRY = `${ACTIONS_DIR}/index.ts`;
@@ -73,6 +81,12 @@ var ACTIONS_ENTRY = `${ACTIONS_DIR}/index.ts`;
73
81
  var SERVER_OPTIONS_FILE = "src/config/serverOptions.ts";
74
82
  /** What Playwright leaves of a run: traces, failures' pictures. */
75
83
  var VISUAL_OUTPUT = `tmp/visual`;
84
+ /**
85
+ * The certificate `plitzi cert` makes for this machine as its network reaches it, and its key — `TLS_CERT` and `TLS_KEY`
86
+ * in `.env`. In `tmp/`: made again on each machine, and a key never committed.
87
+ */
88
+ var TLS_CERT_FILE = `tmp/tls/cert.pem`;
89
+ var TLS_KEY_FILE = `tmp/tls/key.pem`;
76
90
  //#endregion
77
91
  //#region src/browser/index.ts
78
92
  /**
@@ -84,7 +98,7 @@ var VISUAL_OUTPUT = `tmp/visual`;
84
98
  * when it loads, so the CLI depends on no driver.
85
99
  */
86
100
  var SCHEMES = ["light", "dark"];
87
- var isChromium = (value) => e$5(value) && typeof value.launch === "function";
101
+ var isChromium = (value) => e$6(value) && typeof value.launch === "function";
88
102
  /** A property of an object or of a function, which a module's exports can be either of. */
89
103
  var propertyOf = (value, key) => (typeof value === "object" || typeof value === "function") && value !== null ? Reflect.get(value, key) : void 0;
90
104
  /** The project's `@playwright/test`'s Chromium, launched — or why there is none. */
@@ -104,13 +118,14 @@ var launchBrowser = async (root) => {
104
118
  return { problem: `Chromium did not start (${error instanceof Error ? error.message.split("\n")[0] : String(error)}): npx playwright install chromium` };
105
119
  }
106
120
  };
107
- /** What `npm start` wrote down when it took a port: the port, and the name its `/health` answers with. */
121
+ /** What `npm start` wrote down when it took a port: the port, where it answers, and the name its `/health` answers with. */
108
122
  var recorded = async (root) => {
109
123
  try {
110
124
  const value = JSON.parse(await readFile(path.join(root, x$3), "utf8"));
111
- if (!e$5(value)) return {};
125
+ if (!e$6(value)) return {};
112
126
  return {
113
127
  ...typeof value.port === "number" ? { port: value.port } : {},
128
+ ...typeof value.url === "string" ? { url: value.url } : {},
114
129
  ...typeof value.name === "string" ? { name: value.name } : {}
115
130
  };
116
131
  } catch {
@@ -118,18 +133,50 @@ var recorded = async (root) => {
118
133
  }
119
134
  };
120
135
  /**
121
- * Where the project's server answers: `PORT` when set, the port `npm start` took otherwise, else the mode's default —
122
- * and, when `npm start` named itself, that what answers there is this project. Something else on the port answers too,
123
- * with a page and a `200`, and a picture of the wrong server looks like one of the right one.
136
+ * A server of this machine serving HTTPS: a project's own, with a certificate made for the browsers of its network
137
+ * (mkcert) that nothing here was told to trust. What answers is checked by its name instead (`/health`).
138
+ */
139
+ var isLocalTls = (origin) => {
140
+ const { protocol, hostname } = new URL(origin);
141
+ return protocol === "https:" && [
142
+ "127.0.0.1",
143
+ "localhost",
144
+ "[::1]"
145
+ ].includes(hostname);
146
+ };
147
+ /** `/health`, read the way the tools read the pages: a local certificate taken as it is. */
148
+ var readHealth = (origin) => {
149
+ if (!isLocalTls(origin)) return fetch(`${origin}/health`).then((response) => response.json()).catch(() => null);
150
+ return new Promise((resolve) => {
151
+ https.get(`${origin}/health`, { rejectUnauthorized: false }, (response) => {
152
+ let body = "";
153
+ response.setEncoding("utf8");
154
+ response.on("data", (chunk) => body += chunk);
155
+ response.on("end", () => {
156
+ try {
157
+ resolve(JSON.parse(body));
158
+ } catch {
159
+ resolve(null);
160
+ }
161
+ });
162
+ }).on("error", () => resolve(null));
163
+ });
164
+ };
165
+ /**
166
+ * Where the project's server answers: where `npm start` said it does — scheme included, `https` for a project serving
167
+ * a certificate — on `PORT` when that is set, else the mode's default; and, when `npm start` named itself, that what
168
+ * answers there is this project. Something else on the port answers too, with a page and a `200`, and a picture of the
169
+ * wrong server looks like one of the right one.
124
170
  */
125
171
  var projectOrigin = async (root, project) => {
126
172
  const server = await recorded(root);
127
- const port = Number(process.env.PORT ?? server.port ?? (project?.mode === "client" ? 5173 : 8080));
128
- const origin = `http://127.0.0.1:${String(port)}`;
173
+ const where = new URL(server.url ?? "http://127.0.0.1");
174
+ where.port = String(process.env.PORT ?? server.port ?? (project?.mode === "client" ? 5173 : 8080));
175
+ const { origin, port } = where;
129
176
  if (!server.name) return { origin };
130
- const health = await fetch(`${origin}/health`).then((response) => response.json()).catch(() => null);
131
- const answered = e$5(health) && typeof health.Server === "string" ? health.Server : "";
132
- if (answered !== server.name) return { problem: answered ? `Port ${String(port)} answers as "${answered}", not this project ("${server.name}"). Start it: npm start` : `Nothing of this project answers on port ${String(port)}. Start it: npm start` };
177
+ const health = await readHealth(origin);
178
+ const answered = e$6(health) && typeof health.Server === "string" ? health.Server : "";
179
+ if (answered !== server.name) return { problem: answered ? `Port ${port} answers as "${answered}", not this project ("${server.name}"). Start it: npm start` : `Nothing of this project answers on port ${port}. Start it: npm start` };
133
180
  return { origin };
134
181
  };
135
182
  /** A picture as a data URL, which is how a page in the browser is handed one to read. */
@@ -149,10 +196,11 @@ var openProjectPage = async (browser, origin, view) => {
149
196
  height: view.height
150
197
  },
151
198
  colorScheme: view.scheme ?? "light",
152
- reducedMotion: view.reducedMotion ? "reduce" : "no-preference"
199
+ reducedMotion: view.reducedMotion ? "reduce" : "no-preference",
200
+ ignoreHTTPSErrors: isLocalTls(origin)
153
201
  });
154
202
  await page.context().addCookies([...view.scheme ? [{
155
- name: t$5,
203
+ name: t$7,
156
204
  value: view.scheme,
157
205
  url: origin
158
206
  }] : [], ...view.presence ? [] : [{
@@ -172,7 +220,7 @@ var paintedScheme = (page, machine) => page.evaluate((prefers) => {
172
220
  return classList.contains("light") ? "light" : prefers;
173
221
  }, machine);
174
222
  /** The page as a visitor sees it: the dev tools' own chrome — the badge, the panel — left out of whatever is looked at. */
175
- var withoutDevTools = (page) => page.addStyleTag({ content: t$7 });
223
+ var withoutDevTools = (page) => page.addStyleTag({ content: t$8 });
176
224
  //#endregion
177
225
  //#region src/commands/terminal.ts
178
226
  /**
@@ -1027,10 +1075,10 @@ var indexHtml$1 = ({ name }) => `<!doctype html>
1027
1075
  </body>
1028
1076
  </html>
1029
1077
  `;
1030
- var viteConfig$1 = () => `import { createReadStream } from 'node:fs';
1078
+ var viteConfig$1 = () => `import { createReadStream, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
1031
1079
  import { createRequire } from 'node:module';
1032
1080
 
1033
- import { defineConfig } from 'vite';
1081
+ import { defineConfig, loadEnv } from 'vite';
1034
1082
 
1035
1083
  import type { Plugin } from 'vite';
1036
1084
 
@@ -1058,16 +1106,52 @@ const devToolsStylesheet = (): Plugin => ({
1058
1106
  }
1059
1107
  });
1060
1108
 
1109
+ /**
1110
+ * Writes where Vite serves the page — the port it took, 5173 or the next free one, and the scheme — to
1111
+ * \`${x$3}\`, where \`check\`, \`shot\` and \`visual\` look for it.
1112
+ */
1113
+ const recordServer = (): Plugin => ({
1114
+ name: 'plitzi-record-server',
1115
+ apply: 'serve',
1116
+ configureServer(server) {
1117
+ server.httpServer?.once('listening', () => {
1118
+ const address = server.httpServer?.address();
1119
+ if (!address || typeof address === 'string') {
1120
+ return;
1121
+ }
1122
+
1123
+ const scheme = server.config.server.https ? 'https' : 'http';
1124
+ mkdirSync('tmp', { recursive: true });
1125
+ writeFileSync(
1126
+ '${x$3}',
1127
+ \`\${JSON.stringify({ port: address.port, url: \`\${scheme}://127.0.0.1:\${String(address.port)}\` }, null, 2)}\\n\`
1128
+ );
1129
+ });
1130
+ }
1131
+ });
1132
+
1061
1133
  /**
1062
1134
  * Otherwise nothing Plitzi-specific: the SDK is an ordinary dependency, so this is a plain Vite app.
1063
1135
  *
1064
- * The host is pinned because Vite binds \`localhost\` — which on most machines is IPv6 — while everything that
1065
- * waits for a dev server to come up asks for 127.0.0.1. The two resolve differently, so the visual suite sat
1066
- * there watching an address nothing was listening on until it gave up.
1136
+ * \`.env\` says where it listens, as a server project's does. \`HOST\` left out, 127.0.0.1: Vite binds \`localhost\` —
1137
+ * which on most machines is IPv6 — while everything that waits for a dev server to come up asks for 127.0.0.1, and the
1138
+ * visual suite sat there watching an address nothing was listening on. \`HOST=0.0.0.0\` opens it to the network (a
1139
+ * phone on the Wi-Fi); \`TLS_CERT\` and \`TLS_KEY\` (\`plitzi cert\`) serve it over HTTPS, which that phone's browser
1140
+ * asks of a page before it gives it the microphone, the camera or the clipboard.
1067
1141
  */
1068
- export default defineConfig({
1069
- plugins: [devToolsStylesheet()],
1070
- server: { host: '127.0.0.1', port: 5173 }
1142
+ export default defineConfig(({ mode }) => {
1143
+ const env = loadEnv(mode, process.cwd(), '');
1144
+
1145
+ return {
1146
+ plugins: [devToolsStylesheet(), recordServer()],
1147
+ server: {
1148
+ host: env.HOST || '127.0.0.1',
1149
+ port: 5173,
1150
+ ...(env.TLS_CERT && env.TLS_KEY
1151
+ ? { https: { cert: readFileSync(env.TLS_CERT), key: readFileSync(env.TLS_KEY) } }
1152
+ : {})
1153
+ }
1154
+ };
1071
1155
  });
1072
1156
  `;
1073
1157
  var preflightCss$1 = () => `/*
@@ -1299,6 +1383,8 @@ point is looked for. One changed is shown as a diff and left, so changing one is
1299
1383
  whole project to what follows — and to what Node, the server and \`space push\` need of it — and says what to fix.
1300
1384
 
1301
1385
  - \`${MAIN_FILE}\` (in \`src/\`) — the entry point: ${server ? "the page server — the space authored and handed to `serveProject` (`@plitzi/sdk-server/project`), which wires its plugins, its data and its code from where they are" : "the Vite app that renders the space"}.${answers.source === "local" ? `\n- \`${r$7}\` — \`npm run author\`: authors \`src/space/\` and says what it found${server ? "; the server runs it on every save, and is handed the documents it serves next" : ""}.` : ""}${server ? `\n- \`${n$7}/assets.d.ts\` — the types of what a plugin imports besides code: a stylesheet, an image, \`?raw\`, \`?inline\`.` : `\n- \`${n$7}/preflight.css\` — the page's base styles.`}
1386
+ - \`${TSCONFIG_BASE}\` — the compiler options, which \`tsconfig.json\` extends. \`tsconfig.json\` is yours: a folder of
1387
+ your own (\`scripts/\`, \`tools/\`) goes in its \`include\`.
1302
1388
 
1303
1389
  ${server ? "What the server does besides serving the space is yours, in `src/config/serverOptions.ts`.\n\n" : ""}${PLUGINS(answers)}
1304
1390
  ${server ? `${FUNCTIONS}\n${RUNTIME}\n` : ""}${DATA(answers)}${answers.fromSpace ? `\n${FROM_SPACE}` : ""}`;
@@ -1322,9 +1408,9 @@ var MACHINERY = /* @__PURE__ */ new Set([
1322
1408
  `${n$7}/preflight.css`,
1323
1409
  `${n$7}/assets.d.ts`,
1324
1410
  `${n$7}/README.md`,
1411
+ TSCONFIG_BASE,
1325
1412
  "index.html",
1326
1413
  "vite.config.ts",
1327
- "tsconfig.json",
1328
1414
  "tsconfig.build.json",
1329
1415
  ".gitignore",
1330
1416
  "AGENTS.md",
@@ -1443,8 +1529,8 @@ var pluginNames = (packageName) => {
1443
1529
  };
1444
1530
  //#endregion
1445
1531
  //#region ../../packages/sdk-shared/dist/helpers/scroll.mjs
1446
- var e$3 = ["smooth", "auto"];
1447
- var t$3 = [
1532
+ var e$5 = ["smooth", "auto"];
1533
+ var t$5 = [
1448
1534
  "start",
1449
1535
  "center",
1450
1536
  "end",
@@ -1456,7 +1542,7 @@ var r$5 = {
1456
1542
  type: "select",
1457
1543
  description: "How it moves: \"smooth\" glides there, \"auto\" jumps.",
1458
1544
  default: "smooth",
1459
- options: [...e$3]
1545
+ options: [...e$5]
1460
1546
  };
1461
1547
  var a$3 = {
1462
1548
  setState: {
@@ -1560,13 +1646,13 @@ var a$3 = {
1560
1646
  type: "select",
1561
1647
  description: "Where the element lands, top to bottom.",
1562
1648
  default: "start",
1563
- options: [...t$3]
1649
+ options: [...t$5]
1564
1650
  },
1565
1651
  inline: {
1566
1652
  type: "select",
1567
1653
  description: "Where it lands, side to side.",
1568
1654
  default: "nearest",
1569
- options: [...t$3]
1655
+ options: [...t$5]
1570
1656
  },
1571
1657
  behavior: r$5
1572
1658
  }
@@ -2673,6 +2759,12 @@ var projectScripts = (answers) => {
2673
2759
  */
2674
2760
  "start:dev": watchedNodeWithEnv(`${watchPaths(answers)} ${MAIN_FILE}`),
2675
2761
  /**
2762
+ * `start:dev` with Node's inspector on 127.0.0.1:9229 — a debugger (VS Code, chrome://inspect) attaches to the
2763
+ * server, and attaches again after each restart. Only the server: the author script a save runs is a process
2764
+ * of its own, started without it.
2765
+ */
2766
+ "start:dev-inspect": watchedNodeWithEnv(`--inspect ${watchPaths(answers)} ${MAIN_FILE}`),
2767
+ /**
2676
2768
  * What production runs: the same entry compiled to JavaScript. Node strips types by loading a TypeScript
2677
2769
  * transformer into the process — ~10 MB a server keeps for its whole life to read one file — so a deployment
2678
2770
  * runs what `build` emitted and carries no TypeScript at all.
@@ -2729,39 +2821,47 @@ var tsconfigBuild = () => `${JSON.stringify({
2729
2821
  include: ["src", `${n$7}/assets.d.ts`],
2730
2822
  exclude: ["src/plugins", "src/functions"]
2731
2823
  }, null, 2)}\n`;
2732
- var tsconfig$1 = ({ mode }) => `${JSON.stringify({
2733
- compilerOptions: {
2734
- target: "ES2023",
2735
- module: "esnext",
2736
- moduleResolution: "bundler",
2737
- strict: true,
2738
- noEmit: true,
2739
- esModuleInterop: true,
2740
- skipLibCheck: true,
2741
- resolveJsonModule: true,
2742
- allowImportingTsExtensions: true,
2743
- verbatimModuleSyntax: true,
2744
- erasableSyntaxOnly: true,
2745
- /**
2746
- * `vite/client` in the browser build, and it is not optional there: it is what declares a side-effect CSS
2747
- * import and `import.meta.env`, both of which the entry point uses.
2748
- */
2749
- types: mode === "client" ? ["node", "vite/client"] : ["node"],
2750
- lib: [
2751
- "ES2023",
2752
- "DOM",
2753
- "DOM.Iterable"
2754
- ],
2755
- jsx: "react-jsx"
2756
- },
2757
- include: [
2824
+ /**
2825
+ * The compiler options the CLI keeps up, in its own folder: the project's `tsconfig.json` extends them and says what
2826
+ * is read — so a folder of the project's own (`scripts/`, `tools/`) is added there without touching a file of the CLI's.
2827
+ */
2828
+ var tsconfigBase = ({ mode }) => `${JSON.stringify({ compilerOptions: {
2829
+ target: "ES2023",
2830
+ module: "esnext",
2831
+ moduleResolution: "bundler",
2832
+ strict: true,
2833
+ noEmit: true,
2834
+ esModuleInterop: true,
2835
+ skipLibCheck: true,
2836
+ resolveJsonModule: true,
2837
+ allowImportingTsExtensions: true,
2838
+ verbatimModuleSyntax: true,
2839
+ erasableSyntaxOnly: true,
2840
+ /**
2841
+ * `vite/client` in the browser build, and it is not optional there: it is what declares a side-effect CSS
2842
+ * import and `import.meta.env`, both of which the entry point uses.
2843
+ */
2844
+ types: mode === "client" ? ["node", "vite/client"] : ["node"],
2845
+ lib: [
2846
+ "ES2023",
2847
+ "DOM",
2848
+ "DOM.Iterable"
2849
+ ],
2850
+ jsx: "react-jsx"
2851
+ } }, null, 2)}\n`;
2852
+ /**
2853
+ * The project's own: the CLI's options, and what the typecheck and the lint read — add a folder of yours here. Written
2854
+ * as the project's formatter writes it, since it is the project's to format.
2855
+ */
2856
+ var tsconfig$1 = ({ mode }) => {
2857
+ return `{\n "extends": "./${TSCONFIG_BASE}",\n "include": [${[
2758
2858
  n$7,
2759
2859
  "src",
2760
2860
  "visual",
2761
2861
  "playwright.config.ts",
2762
2862
  ...mode === "client" ? ["vite.config.ts"] : []
2763
- ]
2764
- }, null, 2)}\n`;
2863
+ ].map((entry) => JSON.stringify(entry)).join(", ")}]\n}\n`;
2864
+ };
2765
2865
  /**
2766
2866
  * Yarn's four lines are Yarn's own recommendation, and they are not decoration: with the `node-modules` linker it
2767
2867
  * writes `.yarn/install-state.gz` into the project, which is a cache and belongs in no repository.
@@ -2772,7 +2872,7 @@ var YARN_IGNORES$1 = "\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases
2772
2872
  * the CLI records about it, and is committed — a clone without it could not pull, push or upgrade.
2773
2873
  */
2774
2874
  var gitignore$1 = ({ mode, packageManager }) => `node_modules\n${v$3}\n.env\ntmp\n${mode === "server" ? `${y$2}\n` : ""}${packageManager === "yarn" ? YARN_IGNORES$1 : ""}`;
2775
- 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.`;
2875
+ 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; \`plitzi cert\` serves it over HTTPS, which that tablet's browser asks of a page before it gives it the microphone, the camera or the clipboard). \`${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" : ""}; \`${runCommand(packageManager, "start:dev-inspect")}\` is the same with Node's inspector open on 127.0.0.1:9229, for a debugger to attach to the server. 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. \`HOST=0.0.0.0\` in \`.env\` opens it to your network — a tablet on the same Wi-Fi — and \`plitzi cert\` serves it over HTTPS, which that tablet's browser asks of a page before it gives it the microphone, the camera or the clipboard.`;
2776
2876
  var spaceSection = (answers) => {
2777
2877
  if (answers.source === "cloud") return `## Where the space comes from
2778
2878
 
@@ -2846,6 +2946,15 @@ ${settingsSection(answers)}
2846
2946
  put together before it touches one, and the CLI's, so it knows what \`plitzi\` can do for it — plugins, packing,
2847
2947
  uploading. Claude Code reads them automatically; \`AGENTS.md\` points any other agent at them.
2848
2948
  `;
2949
+ /** The project's notes as a project starts with them: what they are for, and nothing yet. */
2950
+ var projectNotes = (answers) => `# ${answers.name} — the project's own notes
2951
+
2952
+ What every session working here has to know and the code does not say: how something is built, what must not be undone,
2953
+ how a task here is done. Kept short and true — a note that stopped being true is deleted.
2954
+
2955
+ \`AGENTS.md\` and \`CLAUDE.md\` are the CLI's and \`plitzi upgrade\` replaces them; this file is the project's, and nothing
2956
+ the CLI does touches it.
2957
+ `;
2849
2958
  /**
2850
2959
  * What any agent opening the project reads first, whichever agent it is.
2851
2960
  *
@@ -2861,12 +2970,14 @@ var agentsFile = (answers) => {
2861
2970
  const commands = [
2862
2971
  `| ${code(installCommand(answers.packageManager))} | install |`,
2863
2972
  `| ${run("start")} | serve it |`,
2973
+ ...answers.mode === "server" ? [`| ${run("start:dev-inspect")} | serve it restarting on a save, with Node's inspector on 127.0.0.1:9229 for a debugger |`] : [],
2864
2974
  ...local ? [`| ${run("author")} | author the space and print its warnings and suggestions |`, `| ${run("lint:space")} | how the space's source is written: each practice to change at its file and line |`] : [],
2865
2975
  `| ${run("visual")} | open the page in a browser and check it rendered |`
2866
2976
  ];
2867
2977
  const zeroWarnings = local ? `Zero warnings from ${run("author")}.` : "Zero warnings from authoring.";
2868
2978
  const refusers = [answers.mode === "server" ? "The server" : "", local ? run("author") : ""].filter(Boolean).join(" and ");
2869
- const port = answers.mode === "server" ? `${run("start")} serves on 8080, or on the next free port when something else holds it — printed, and written to ${code(x$3)}, where ${code("check")}, ${code("shot")} and ${code("visual")} read it. ${code("PORT")} chooses one.` : `${run("start")} runs Vite on 5173.`;
2979
+ const network = `${code("HOST=0.0.0.0")} in ${code(".env")} opens it to the network; ${code("plitzi cert")} serves it over HTTPS — over http, a phone's browser gives the page no microphone, camera or clipboard.`;
2980
+ const port = answers.mode === "server" ? `${run("start")} serves on 8080, or on the next free port when something else holds it — printed, and written to ${code(x$3)}, where ${code("check")}, ${code("shot")} and ${code("visual")} read it. ${code("PORT")} chooses one. ${network}` : `${run("start")} runs Vite on 5173, or the next free port — written to ${code(x$3)}. ${network}`;
2870
2981
  const serverNotes = answers.mode === "server" ? `- **The server is yours in ${code("src/config/serverOptions.ts")}**, not in ${code(MAIN_FILE)} — that one is the CLI's, and ${code("plitzi upgrade")} keeps it current: what the server does besides serving the space goes there (what ${code("serveProject")} wires itself is not offered)${local ? `, and the space's server actions are ${code("src/actions/")}` : ""}.\n- **Pictures from other sites** are resized by this server once ${code("src/config/serverOptions.ts")} names their hosts — ${code("images: { domains }")}, a list of hosts like ${code("images.example.com")} — and ${code("sharp")} is installed: an ${code("image")} then offers a ${code("srcset")} (give it ${code("sizes")}, and ${code("width")}/${code("height")} so nothing jumps).\n` : "";
2871
2982
  const dataNote = answers.mode === "server" ? `- **Data with no backend** goes in ${code(`${m}/*.json`)}: the server reads it and never serves it. An ${code("apiContainer")} whose ${code("runtime")} is ${code("server")} and ${code("query")} ${code("/data/products.json")} reads it, and the page arrives with it — bound as ${code("products.data.items")}, on a page or a layout, never inside a component. ${local ? `A browser provider asking for ${code("/data/…")} is refused by ${run("author")}. ` : ""}What a provider reads is in the page it renders: data a page must not carry is read in a server action, which answers only what is shown — a task of ${code("src/functions/")} reads the file with ${code("ctx.data(\"products.json\")")}, never an import.` : `- **Data with no backend** goes in ${code("public/data/*.json")}, fetched by the browser — public like everything in ${code("public/")} — and read by an ${code("apiContainer")} whose ${code("query")} is ${code("/data/products.json")}.`;
2872
2983
  const settingsNote = answers.mode === "server" ? `Node reads ${code(".env")} as a script starts, before any module loads: read ${code("process.env")}, never load the file yourself.` : `Vite reads it and hands the page only ${code("VITE_*")}, which ships in it: never a secret.`;
@@ -2890,6 +3001,7 @@ ${commands.join("\n")}
2890
3001
  ## This project
2891
3002
 
2892
3003
  - **Yours is \`src/\` — but \`${MAIN_FILE}\`, the entry point — and \`${n$7}/\` is the CLI's**: \`plitzi upgrade\` replaces them, so never edit them. \`${n$7}/README.md\` says what each folder of \`src/\` is.
3004
+ - **This project's own notes are ${code(PROJECT_NOTES)}** — read them before you start, and write there what the next session has to know: how something here is built, what must not be undone, how a task here is done. This file and ${code("CLAUDE.md")} are the CLI's and replaced by ${code("plitzi upgrade")}; ${code(PROJECT_NOTES)} never is.
2893
3005
  - **Port.** ${port}
2894
3006
  - **Settings are ${code(".env")}**, never committed; ${code(".env.example")} names them, committed — a new one goes in both. ${settingsNote}
2895
3007
  ${dataNote}
@@ -2920,7 +3032,7 @@ What you leave behind is the next reader's problem — the user's, or the next a
2920
3032
  - **Scratch goes in ${code(`tmp/`)}, or nowhere.** A one-off script, a dump, a picture to look at — never at the root or beside the source, where it reads as part of the project.
2921
3033
  - **One of everything.** A look used twice is a class; a value used twice is a token; a block used twice is a component, and rows of data are one list. Change it where it is defined, and rename everywhere when you rename.
2922
3034
  - **Files a reader can find.** One part per file, named after what it is, in the folder of its kind — the shape ${code("src/space/")} already has. Do not start a parallel layout of your own.
2923
- - **Leave it passing.** ${run("verify")} runs it all and prints only what fails: ${local ? `${run("author")} with zero warnings, ${run("lint:space")} clean, ` : ""}${run("typecheck")}, ${run("lint")} and the format clean, and every page whole (${run("check")}, with the server up) — those for signed-in visitors too, once ${code(".env")} names an account to check them as: ${code("PLITZI_CHECK_USER")} and ${code("PLITZI_CHECK_PASSWORD")}.
3035
+ - **Leave it passing.** ${run("verify")} runs it all and prints only what fails: ${local ? `${run("author")} with zero warnings, ${run("lint:space")} clean, ` : ""}${run("typecheck")}, ${run("lint")} and the format clean, the project's own tests when it has a ${code("test")} script, and every page whole (${run("check")}, with the server up) — those for signed-in visitors too, once ${code(".env")} names an account to check them as: ${code("PLITZI_CHECK_USER")} and ${code("PLITZI_CHECK_PASSWORD")}.
2924
3036
 
2925
3037
  ## The rules that go wrong most
2926
3038
 
@@ -2957,13 +3069,13 @@ PLITZI_ENVIRONMENT=${environment}
2957
3069
  ${revision ? `PLITZI_REVISION=${String(revision)}` : "# PLITZI_REVISION=12"}
2958
3070
 
2959
3071
  ` : ""}${SERVER_SETTINGS}`;
2960
- return source === "cloud" ? `# The space's public render key. It ships in the page by design; the origin the browser states is what
3072
+ return `${source === "cloud" ? `# The space's public render key. It ships in the page by design; the origin the browser states is what
2961
3073
  # protects it — add this project's domain to the space's allowed domains.
2962
3074
  VITE_PLITZI_WEB_KEY=${key}
2963
3075
  VITE_PLITZI_ENVIRONMENT=${environment}
2964
3076
  ` : `# What the page reads as import.meta.env.VITE_*: Vite hands it only what is named VITE_, and each value ships in the
2965
3077
  # page — never a secret.
2966
- `;
3078
+ `}${CLIENT_SETTINGS}`;
2967
3079
  };
2968
3080
  /**
2969
3081
  * `.env.example`: the same settings with no secret in them — committed, where `.env` never is, so a clone knows what to
@@ -2976,6 +3088,15 @@ ${envFile({
2976
3088
  key: ""
2977
3089
  })}`;
2978
3090
  /**
3091
+ * HTTPS while developing, in either mode: what `plitzi cert` writes, offered commented until it has.
3092
+ */
3093
+ var TLS_SETTINGS = `
3094
+ # HTTPS, for that tablet: over http its browser gives a page no microphone, camera or clipboard. \`plitzi cert\` makes a
3095
+ # certificate for this machine and its network addresses, and sets these two.
3096
+ # TLS_CERT=${TLS_CERT_FILE}
3097
+ # TLS_KEY=${TLS_KEY_FILE}
3098
+ `;
3099
+ /**
2979
3100
  * What every server project is given in `.env`: the key its actions sign with — filled by `plitzi create` with one made
2980
3101
  * for it (`withSigningSecret`), never by the scaffold, which is the same for everybody — and the port, left to choose.
2981
3102
  */
@@ -2987,21 +3108,29 @@ PLITZI_SIGNING_SECRET=
2987
3108
 
2988
3109
  # 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.
2989
3110
  # HOST=0.0.0.0
2990
-
3111
+ ${TLS_SETTINGS}
2991
3112
  # The secrets the space's actions and functions name — ctx.fetch's credential, a connector's: credential id → its keys.
2992
3113
  # PLITZI_CREDENTIALS={"google":{"clientId":"…","clientSecret":"…"}}
2993
3114
  `;
3115
+ /** Where Vite listens, as a server project's `.env` says it — Vite's config reads these, not the page. */
3116
+ var CLIENT_SETTINGS = `
3117
+ # 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.
3118
+ # A space read from Plitzi answers that address once it is among the space's allowed domains.
3119
+ # HOST=0.0.0.0
3120
+ ${TLS_SETTINGS}`;
2994
3121
  /** A `.env` with the signing key `plitzi create` made for the project in its place. */
2995
3122
  var withSigningSecret = (env, secret) => env.replace(/^PLITZI_SIGNING_SECRET=$/m, `PLITZI_SIGNING_SECRET=${secret}`);
2996
3123
  var projectFiles = (answers) => ({
2997
3124
  ...managerFiles(answers.packageManager, answers.managerVersion),
2998
3125
  "package.json": packageJson$1(answers),
2999
3126
  "tsconfig.json": tsconfig$1(answers),
3127
+ [TSCONFIG_BASE]: tsconfigBase(answers),
3000
3128
  ...answers.mode === "server" ? { "tsconfig.build.json": tsconfigBuild() } : {},
3001
3129
  ".gitignore": gitignore$1(answers),
3002
3130
  "README.md": readme$1(answers),
3003
3131
  "AGENTS.md": agentsFile(answers),
3004
- "CLAUDE.md": "@AGENTS.md\n",
3132
+ "CLAUDE.md": `@AGENTS.md\n@${PROJECT_NOTES}\n`,
3133
+ [PROJECT_NOTES]: projectNotes(answers),
3005
3134
  ".env": envFile(answers),
3006
3135
  ".env.example": envExample(answers)
3007
3136
  });
@@ -3255,7 +3384,7 @@ var versionOf$2 = (resolvers, packageName) => {
3255
3384
  const manifest = resolveFrom(resolvers, `${packageName}/package.json`);
3256
3385
  if (!manifest) return;
3257
3386
  const parsed = JSON.parse(readFileSync(manifest, "utf-8"));
3258
- return e$5(parsed) && typeof parsed.version === "string" ? parsed.version : void 0;
3387
+ return e$6(parsed) && typeof parsed.version === "string" ? parsed.version : void 0;
3259
3388
  };
3260
3389
  /** The frontmatter's `version:`, said once: after the skill's `name:`, replacing any already there. */
3261
3390
  var stampVersion = (skill, version) => skill.replace(/^version: .*\n/m, "").replace(/^(name: .*)$/m, `$1\nversion: ${version}`);
@@ -3523,8 +3652,8 @@ var spaceFiles = (answers) => {
3523
3652
  * a space renders through a stack — schema, style, plugins, hydration — where a mistake shows up as a blank page
3524
3653
  * rather than as an exception, and nobody writes the first test for a project that already looks fine.
3525
3654
  */
3526
- var RECORDED = `/** What \`npm start\` wrote down when it took a port: the port, and the name its \`/health\` answers with. */
3527
- const recorded = (): { port?: number; name?: string } => {
3655
+ var RECORDED = `/** What \`npm start\` wrote down when it took a port: the port, where it answers and the name its \`/health\` answers with. */
3656
+ const recorded = (): { port?: number; url?: string; name?: string } => {
3528
3657
  try {
3529
3658
  const value: unknown = JSON.parse(readFileSync('${x$3}', 'utf8'));
3530
3659
  if (typeof value !== 'object' || value === null) {
@@ -3533,6 +3662,7 @@ const recorded = (): { port?: number; name?: string } => {
3533
3662
 
3534
3663
  return {
3535
3664
  ...('port' in value && typeof value.port === 'number' ? { port: value.port } : {}),
3665
+ ...('url' in value && typeof value.url === 'string' ? { url: value.url } : {}),
3536
3666
  ...('name' in value && typeof value.name === 'string' ? { name: value.name } : {})
3537
3667
  };
3538
3668
  } catch {
@@ -3547,16 +3677,21 @@ ${RECORDED}
3547
3677
 
3548
3678
  // \`PORT\` when set; otherwise the port \`npm start\` took; otherwise the default.
3549
3679
  const PORT = Number(process.env.PORT ?? recorded().port ?? ${mode === "server" ? "8080" : "5173"});
3680
+ // Where \`npm start\` said it answers — \`https\` when the server has a certificate — on that port.
3681
+ const ORIGIN = new URL(recorded().url ?? 'http://127.0.0.1');
3682
+ ORIGIN.port = String(PORT);
3550
3683
 
3551
3684
  export default defineConfig({
3552
3685
  testDir: './visual',
3553
3686
  outputDir: './${VISUAL_OUTPUT}',
3554
- use: { baseURL: \`http://127.0.0.1:\${PORT}\` },
3687
+ // A local certificate (mkcert) is one this machine's browsers were told to trust, not Playwright's.
3688
+ use: { baseURL: ORIGIN.origin, ignoreHTTPSErrors: true },
3555
3689
  // Playwright starts the project itself, so \`${runCommand(packageManager, "visual")}\` is one command from a
3556
3690
  // cold checkout.
3557
3691
  webServer: {
3558
3692
  command: '${runCommand(packageManager, "start")}',
3559
- url: \`http://127.0.0.1:\${PORT}\`,
3693
+ url: ORIGIN.origin,
3694
+ ignoreHTTPSErrors: true,
3560
3695
  env: { PORT: String(PORT) },
3561
3696
  reuseExistingServer: true,
3562
3697
  timeout: 120_000
@@ -4282,7 +4417,7 @@ var projectHere = async (what) => {
4282
4417
  };
4283
4418
  //#endregion
4284
4419
  //#region src/commands/projectFormatter.ts
4285
- var isPrettier = (value) => e$5(value) && [
4420
+ var isPrettier = (value) => e$6(value) && [
4286
4421
  "format",
4287
4422
  "resolveConfig",
4288
4423
  "getFileInfo"
@@ -4297,7 +4432,7 @@ var projectFormatter = async (root) => {
4297
4432
  } catch {
4298
4433
  return unformatted;
4299
4434
  }
4300
- const prettier = isPrettier(loaded) ? loaded : e$5(loaded) ? loaded.default : void 0;
4435
+ const prettier = isPrettier(loaded) ? loaded : e$6(loaded) ? loaded.default : void 0;
4301
4436
  if (!isPrettier(prettier)) return unformatted;
4302
4437
  const api = prettier;
4303
4438
  const ignorePath = [".prettierignore", ".gitignore"].map((file) => path.join(root, file));
@@ -4316,8 +4451,8 @@ var projectFormatter = async (root) => {
4316
4451
  };
4317
4452
  //#endregion
4318
4453
  //#region ../../packages/sdk-authoring/dist/index-declare.js
4319
- var e$2 = () => (e) => e;
4320
- var t$2 = () => ({
4454
+ var e$3 = () => (e) => e;
4455
+ var t$3 = () => ({
4321
4456
  canDelete: !0,
4322
4457
  canSelect: !0,
4323
4458
  canDragDrop: !0,
@@ -4344,7 +4479,7 @@ var r$3 = () => (e) => {
4344
4479
  ...e.content,
4345
4480
  definition: n$3(e.type, r),
4346
4481
  builder: {
4347
- ...t$2(),
4482
+ ...t$3(),
4348
4483
  ...i
4349
4484
  },
4350
4485
  market: {
@@ -4377,22 +4512,22 @@ var __exportAll = (all, no_symbols) => {
4377
4512
  return target;
4378
4513
  };
4379
4514
  var e$42 = "[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*";
4380
- var t$62 = `(?:${e$42}|\\d+)`;
4381
- var n$47 = `\\[\\s*(?:\\d+|'[^']*'|"[^"]*"|${e$42}(?:\\.${t$62})*)\\s*\\]`;
4382
- var r$36 = `${`${e$42}(?:${n$47})*(?:\\??\\.${t$62}(?:${n$47})*)*`}(?:\\s*\\?\\?\\s*[^|{}]+?)?(?:\\s*\\|\\s*[a-zA-Z_]+(?:\\([^)]*\\))?)*`;
4383
- var i$29 = `\\{\\{\\s*${r$36}\\s*\\}\\}`;
4384
- var a$24 = `(?:${`\\{\\{\\{\\s*${r$36}\\s*\\}\\}\\}`}|${i$29})`;
4515
+ var t$63 = `(?:${e$42}|\\d+)`;
4516
+ var n$48 = `\\[\\s*(?:\\d+|'[^']*'|"[^"]*"|${e$42}(?:\\.${t$63})*)\\s*\\]`;
4517
+ var r$37 = `${`${e$42}(?:${n$48})*(?:\\??\\.${t$63}(?:${n$48})*)*`}(?:\\s*\\?\\?\\s*[^|{}]+?)?(?:\\s*\\|\\s*[a-zA-Z_]+(?:\\([^)]*\\))?)*`;
4518
+ var i$29 = `\\{\\{\\s*${r$37}\\s*\\}\\}`;
4519
+ var a$24 = `(?:${`\\{\\{\\{\\s*${r$37}\\s*\\}\\}\\}`}|${i$29})`;
4385
4520
  new RegExp(a$24, "g");
4386
4521
  RegExp(`^${a$24}$`);
4387
4522
  var e$41 = ["smooth", "auto"];
4388
- var t$60 = [
4523
+ var t$61 = [
4389
4524
  "start",
4390
4525
  "center",
4391
4526
  "end",
4392
4527
  "nearest"
4393
4528
  ];
4394
4529
  var s$22 = "onScroll";
4395
- var r$34 = {
4530
+ var r$35 = {
4396
4531
  type: "select",
4397
4532
  description: "How it moves: \"smooth\" glides there, \"auto\" jumps.",
4398
4533
  default: "smooth",
@@ -4470,7 +4605,7 @@ var a$22 = {
4470
4605
  description: "How far down, the same way; negative goes up.",
4471
4606
  default: ""
4472
4607
  },
4473
- behavior: r$34
4608
+ behavior: r$35
4474
4609
  }
4475
4610
  },
4476
4611
  scrollTo: {
@@ -4488,7 +4623,7 @@ var a$22 = {
4488
4623
  description: "Where down, the same way. Empty stays.",
4489
4624
  default: ""
4490
4625
  },
4491
- behavior: r$34
4626
+ behavior: r$35
4492
4627
  }
4493
4628
  },
4494
4629
  scrollIntoView: {
@@ -4500,15 +4635,15 @@ var a$22 = {
4500
4635
  type: "select",
4501
4636
  description: "Where the element lands, top to bottom.",
4502
4637
  default: "start",
4503
- options: [...t$60]
4638
+ options: [...t$61]
4504
4639
  },
4505
4640
  inline: {
4506
4641
  type: "select",
4507
4642
  description: "Where it lands, side to side.",
4508
4643
  default: "nearest",
4509
- options: [...t$60]
4644
+ options: [...t$61]
4510
4645
  },
4511
- behavior: r$34
4646
+ behavior: r$35
4512
4647
  }
4513
4648
  }
4514
4649
  };
@@ -4682,7 +4817,7 @@ var e$40 = {
4682
4817
  params: {}
4683
4818
  }
4684
4819
  };
4685
- var t$59 = r$3()({
4820
+ var t$60 = r$3()({
4686
4821
  type: "blockHtml",
4687
4822
  content: {
4688
4823
  attributes: { content: "" },
@@ -4697,7 +4832,7 @@ var t$59 = r$3()({
4697
4832
  defaultStyle: { style: { base: { default: {} } } }
4698
4833
  }
4699
4834
  });
4700
- var t$58 = r$3()({
4835
+ var t$59 = r$3()({
4701
4836
  type: "blockJsx",
4702
4837
  content: {
4703
4838
  attributes: {
@@ -4717,7 +4852,7 @@ var t$58 = r$3()({
4717
4852
  defaultStyle: { style: { base: { default: {} } } }
4718
4853
  }
4719
4854
  });
4720
- var t$57 = r$3()({
4855
+ var t$58 = r$3()({
4721
4856
  type: "custom",
4722
4857
  content: {
4723
4858
  attributes: {
@@ -4745,7 +4880,7 @@ var t$57 = r$3()({
4745
4880
  }
4746
4881
  }
4747
4882
  });
4748
- var t$56 = r$3()({
4883
+ var t$57 = r$3()({
4749
4884
  type: "nodeHtml",
4750
4885
  content: {
4751
4886
  attributes: { subType: "div" },
@@ -4768,6 +4903,24 @@ var t$56 = r$3()({
4768
4903
  }
4769
4904
  }
4770
4905
  });
4906
+ var t$56 = r$3()({
4907
+ type: "plitziSdk",
4908
+ content: {
4909
+ attributes: {
4910
+ spaceKey: "",
4911
+ environment: "main"
4912
+ },
4913
+ definition: {
4914
+ label: "Plitzi Sdk",
4915
+ description: "Another space drawn inside this one, as a widget — its home page, no router of its own: by its key, or from its documents (`offlineData`), bound to UI generated on the fly."
4916
+ },
4917
+ market: {
4918
+ category: "advanced",
4919
+ icon: "https://cdn.plitzi.com/resources/img/favicon.svg"
4920
+ },
4921
+ defaultStyle: { style: { base: { default: {} } } }
4922
+ }
4923
+ });
4771
4924
  var t$55 = r$3()({
4772
4925
  type: "reference",
4773
4926
  content: {
@@ -4790,7 +4943,7 @@ var t$55 = r$3()({
4790
4943
  }
4791
4944
  }
4792
4945
  });
4793
- var n$44 = {
4946
+ var n$45 = {
4794
4947
  "padding-top": "6px",
4795
4948
  "padding-left": "12px",
4796
4949
  "padding-right": "12px",
@@ -4803,16 +4956,16 @@ var n$44 = {
4803
4956
  "border-bottom-left-radius": "4px",
4804
4957
  "border-bottom-right-radius": "4px"
4805
4958
  };
4806
- var r$33 = r$3()({
4959
+ var r$34 = r$3()({
4807
4960
  type: "button",
4808
4961
  attributeValues: {
4809
- subType: e$2()([
4962
+ subType: e$3()([
4810
4963
  "button",
4811
4964
  "submit",
4812
4965
  "reset"
4813
4966
  ]),
4814
- contentPlacement: e$2()(["before", "after"]),
4815
- iconPlacement: e$2()(["before", "after"])
4967
+ contentPlacement: e$3()(["before", "after"]),
4968
+ iconPlacement: e$3()(["before", "after"])
4816
4969
  },
4817
4970
  content: {
4818
4971
  attributes: {
@@ -4834,30 +4987,30 @@ var r$33 = r$3()({
4834
4987
  icon: "fa-solid fa-hand-pointer"
4835
4988
  },
4836
4989
  defaultStyle: {
4837
- style: { base: { default: n$44 } },
4990
+ style: { base: { default: n$45 } },
4838
4991
  subTypes: {
4839
4992
  button: {
4840
4993
  name: "Default Button",
4841
4994
  displayMode: "desktop",
4842
- style: { base: { default: n$44 } }
4995
+ style: { base: { default: n$45 } }
4843
4996
  },
4844
4997
  reset: {
4845
4998
  name: "Reset Button",
4846
4999
  displayMode: "desktop",
4847
- style: { base: { default: n$44 } }
5000
+ style: { base: { default: n$45 } }
4848
5001
  },
4849
5002
  submit: {
4850
5003
  name: "Submit Button",
4851
5004
  displayMode: "desktop",
4852
- style: { base: { default: n$44 } }
5005
+ style: { base: { default: n$45 } }
4853
5006
  }
4854
5007
  }
4855
5008
  }
4856
5009
  }
4857
5010
  });
4858
- var n$43 = r$3()({
5011
+ var n$44 = r$3()({
4859
5012
  type: "dropdown",
4860
- attributeValues: { popupPlacement: e$2()([
5013
+ attributeValues: { popupPlacement: e$3()([
4861
5014
  "left",
4862
5015
  "right",
4863
5016
  "top",
@@ -4947,9 +5100,9 @@ var t$54 = r$3()({
4947
5100
  },
4948
5101
  initialItems: []
4949
5102
  });
4950
- var n$42 = r$3()({
5103
+ var n$43 = r$3()({
4951
5104
  type: "heading",
4952
- attributeValues: { subType: e$2()([
5105
+ attributeValues: { subType: e$3()([
4953
5106
  "h1",
4954
5107
  "h2",
4955
5108
  "h3",
@@ -5047,23 +5200,23 @@ var n$42 = r$3()({
5047
5200
  }
5048
5201
  }
5049
5202
  });
5050
- var n$41 = r$3()({
5203
+ var n$42 = r$3()({
5051
5204
  type: "link",
5052
5205
  attributeValues: {
5053
- mode: e$2()([
5206
+ mode: e$3()([
5054
5207
  "page",
5055
5208
  "internal",
5056
5209
  "external"
5057
5210
  ]),
5058
- target: e$2()([
5211
+ target: e$3()([
5059
5212
  "self",
5060
5213
  "blank",
5061
5214
  "parent",
5062
5215
  "top"
5063
5216
  ]),
5064
- contentPlacement: e$2()(["before", "after"]),
5065
- iconPlacement: e$2()(["before", "after"]),
5066
- current: e$2()(["page", "section"])
5217
+ contentPlacement: e$3()(["before", "after"]),
5218
+ iconPlacement: e$3()(["before", "after"]),
5219
+ current: e$3()(["page", "section"])
5067
5220
  },
5068
5221
  content: {
5069
5222
  attributes: {
@@ -5163,9 +5316,9 @@ var t$52 = r$3()({
5163
5316
  } } } }
5164
5317
  }
5165
5318
  });
5166
- var n$40 = r$3()({
5319
+ var n$41 = r$3()({
5167
5320
  type: "richText",
5168
- attributeValues: { format: e$2()([
5321
+ attributeValues: { format: e$3()([
5169
5322
  "html",
5170
5323
  "markdown",
5171
5324
  "text"
@@ -5235,7 +5388,7 @@ var t$51 = r$3()({
5235
5388
  defaultStyle: { style: { base: { default: { display: "inline" } } } }
5236
5389
  }
5237
5390
  });
5238
- var n$39 = {
5391
+ var n$40 = {
5239
5392
  "margin-top": "0px",
5240
5393
  "margin-right": "0px",
5241
5394
  "margin-bottom": "0px",
@@ -5256,9 +5409,9 @@ var n$39 = {
5256
5409
  "letter-spacing": "inherit",
5257
5410
  cursor: "pointer"
5258
5411
  };
5259
- var r$32 = r$3()({
5412
+ var r$33 = r$3()({
5260
5413
  type: "themeToggle",
5261
- attributeValues: { subType: e$2()(["switch", "segmented"]) },
5414
+ attributeValues: { subType: e$3()(["switch", "segmented"]) },
5262
5415
  triggers: { onThemeChange: {
5263
5416
  action: "onThemeChange",
5264
5417
  title: "On Theme Change",
@@ -5289,10 +5442,10 @@ var r$32 = r$3()({
5289
5442
  },
5290
5443
  defaultStyle: {
5291
5444
  style: {
5292
- base: { default: n$39 },
5445
+ base: { default: n$40 },
5293
5446
  icon: { default: {} },
5294
5447
  option: { default: {
5295
- ...n$39,
5448
+ ...n$40,
5296
5449
  "row-gap": "4px",
5297
5450
  "column-gap": "4px"
5298
5451
  } }
@@ -5301,9 +5454,9 @@ var r$32 = r$3()({
5301
5454
  }
5302
5455
  }
5303
5456
  });
5304
- var n$38 = r$3()({
5457
+ var n$39 = r$3()({
5305
5458
  type: "form",
5306
- attributeValues: { method: e$2()(["get", "post"]) },
5459
+ attributeValues: { method: e$3()(["get", "post"]) },
5307
5460
  sourceType: "apiContainer",
5308
5461
  triggers: { onSubmit: {
5309
5462
  action: "onSubmit",
@@ -5381,7 +5534,7 @@ var n$38 = r$3()({
5381
5534
  defaultStyle: { style: { base: { default: {} } } }
5382
5535
  }
5383
5536
  });
5384
- var n$37 = {
5537
+ var n$38 = {
5385
5538
  width: "100%",
5386
5539
  display: "flex",
5387
5540
  "align-items": "center",
@@ -5410,8 +5563,8 @@ var n$37 = {
5410
5563
  outline: "none",
5411
5564
  position: "relative"
5412
5565
  };
5413
- var r$31 = {
5414
- ...n$37,
5566
+ var r$32 = {
5567
+ ...n$38,
5415
5568
  "line-height": "20px"
5416
5569
  };
5417
5570
  var i$26 = {
@@ -5494,7 +5647,7 @@ var d$12 = {
5494
5647
  cursor: "pointer"
5495
5648
  };
5496
5649
  var f$10 = {
5497
- ...n$37,
5650
+ ...n$38,
5498
5651
  cursor: "pointer"
5499
5652
  };
5500
5653
  var p$11 = {
@@ -5543,7 +5696,7 @@ var m$7 = (e, t, { field: n, icon: r } = {}) => ({
5543
5696
  var h$4 = { field: l$13 };
5544
5697
  var g$4 = r$3()({
5545
5698
  type: "formControl",
5546
- attributeValues: { subType: e$2()([
5699
+ attributeValues: { subType: e$3()([
5547
5700
  "text",
5548
5701
  "number",
5549
5702
  "email",
@@ -5616,21 +5769,21 @@ var g$4 = r$3()({
5616
5769
  style: { base: { default: {} } },
5617
5770
  subTypes: {
5618
5771
  hidden: m$7("Form Control Hidden", {}),
5619
- text: m$7("Form Control Text", r$31, h$4),
5620
- number: m$7("Form Control Number", r$31, h$4),
5621
- email: m$7("Form Control Email", r$31, h$4),
5622
- password: m$7("Form Control Password", r$31, {
5772
+ text: m$7("Form Control Text", r$32, h$4),
5773
+ number: m$7("Form Control Number", r$32, h$4),
5774
+ email: m$7("Form Control Email", r$32, h$4),
5775
+ password: m$7("Form Control Password", r$32, {
5623
5776
  ...h$4,
5624
5777
  icon: d$12
5625
5778
  }),
5626
- search: m$7("Form Control Search", r$31, h$4),
5627
- url: m$7("Form Control URL", r$31, h$4),
5628
- tel: m$7("Form Control Phone", r$31, h$4),
5629
- date: m$7("Form Control Date", r$31, h$4),
5630
- time: m$7("Form Control Time", r$31, h$4),
5631
- color: m$7("Form Control Color", r$31, h$4),
5779
+ search: m$7("Form Control Search", r$32, h$4),
5780
+ url: m$7("Form Control URL", r$32, h$4),
5781
+ tel: m$7("Form Control Phone", r$32, h$4),
5782
+ date: m$7("Form Control Date", r$32, h$4),
5783
+ time: m$7("Form Control Time", r$32, h$4),
5784
+ color: m$7("Form Control Color", r$32, h$4),
5632
5785
  textarea: m$7("Form Control Textarea", {
5633
- ...r$31,
5786
+ ...r$32,
5634
5787
  color: "inherit",
5635
5788
  resize: "vertical"
5636
5789
  }),
@@ -5669,9 +5822,9 @@ var g$4 = r$3()({
5669
5822
  }
5670
5823
  }
5671
5824
  });
5672
- var n$36 = r$3()({
5825
+ var n$37 = r$3()({
5673
5826
  type: "layoutContainer",
5674
- attributeValues: { subType: e$2()([
5827
+ attributeValues: { subType: e$3()([
5675
5828
  "div",
5676
5829
  "header",
5677
5830
  "footer",
@@ -5737,13 +5890,14 @@ var t$49 = r$3()({
5737
5890
  } } } }
5738
5891
  }
5739
5892
  });
5740
- var t$48 = r$3()({
5893
+ var r$31 = r$3()({
5741
5894
  type: "page",
5742
5895
  serverTemplates: [
5743
5896
  "seoPageTitle",
5744
5897
  "seoPageDescription",
5745
5898
  "notFound"
5746
5899
  ],
5900
+ attributeValues: { documentScroll: ["locked", "noBounce"] },
5747
5901
  triggers: { onPageLoad: {
5748
5902
  action: "onPageLoad",
5749
5903
  title: "On Page Load",
@@ -5847,12 +6001,12 @@ var t$46 = r$3()({
5847
6001
  var n$35 = r$3()({
5848
6002
  type: "image",
5849
6003
  attributeValues: {
5850
- fetchPriority: e$2()([
6004
+ fetchPriority: e$3()([
5851
6005
  "high",
5852
6006
  "low",
5853
6007
  "auto"
5854
6008
  ]),
5855
- loadMode: e$2()([
6009
+ loadMode: e$3()([
5856
6010
  "auto",
5857
6011
  "eager",
5858
6012
  "lazy"
@@ -5935,7 +6089,7 @@ var t$44 = r$3()({
5935
6089
  var n$34 = r$3()({
5936
6090
  type: "apiContainer",
5937
6091
  attributeValues: {
5938
- subType: e$2()([
6092
+ subType: e$3()([
5939
6093
  "",
5940
6094
  "div",
5941
6095
  "header",
@@ -5948,19 +6102,19 @@ var n$34 = r$3()({
5948
6102
  "address",
5949
6103
  "figure"
5950
6104
  ]),
5951
- method: e$2()([
6105
+ method: e$3()([
5952
6106
  "get",
5953
6107
  "post",
5954
6108
  "put",
5955
6109
  "delete",
5956
6110
  "patch"
5957
6111
  ]),
5958
- credentials: e$2()([
6112
+ credentials: e$3()([
5959
6113
  "include",
5960
6114
  "omit",
5961
6115
  "same-origin"
5962
6116
  ]),
5963
- pagination: e$2()([
6117
+ pagination: e$3()([
5964
6118
  "none",
5965
6119
  "url",
5966
6120
  "append"
@@ -6088,7 +6242,7 @@ var n$34 = r$3()({
6088
6242
  });
6089
6243
  var n$33 = r$3()({
6090
6244
  type: "channel",
6091
- attributeValues: { subType: e$2()([
6245
+ attributeValues: { subType: e$3()([
6092
6246
  "",
6093
6247
  "div",
6094
6248
  "section",
@@ -6295,7 +6449,7 @@ var t$42 = r$3()({
6295
6449
  var r$30 = r$3()({
6296
6450
  type: "container",
6297
6451
  attributeValues: {
6298
- subType: e$2()([
6452
+ subType: e$3()([
6299
6453
  "div",
6300
6454
  "header",
6301
6455
  "footer",
@@ -6319,7 +6473,7 @@ var r$30 = r$3()({
6319
6473
  "p",
6320
6474
  "span"
6321
6475
  ]),
6322
- live: e$2()([
6476
+ live: e$3()([
6323
6477
  "",
6324
6478
  "polite",
6325
6479
  "assertive"
@@ -6384,16 +6538,16 @@ var n$31 = {
6384
6538
  outline: "none",
6385
6539
  cursor: "pointer"
6386
6540
  };
6387
- var B$2 = {
6541
+ var V$2 = {
6388
6542
  ApiContainer: n$34,
6389
- BlockHtml: t$59,
6390
- BlockJsx: t$58,
6391
- Button: r$33,
6543
+ BlockHtml: t$60,
6544
+ BlockJsx: t$59,
6545
+ Button: r$34,
6392
6546
  Carousel: t$42,
6393
6547
  CarouselTrack: t$43,
6394
6548
  Channel: n$33,
6395
6549
  Container: r$30,
6396
- Custom: t$57,
6550
+ Custom: t$58,
6397
6551
  DialogContainer: r$3()({
6398
6552
  type: "dialogContainer",
6399
6553
  sourceType: "dialogContainer",
@@ -6560,21 +6714,21 @@ var B$2 = {
6560
6714
  },
6561
6715
  initialItems: ["container"]
6562
6716
  }),
6563
- Dropdown: n$43,
6717
+ Dropdown: n$44,
6564
6718
  DropdownPopup: t$54,
6565
6719
  Embed: t$47,
6566
6720
  FontAwesome: t$46,
6567
- Form: n$38,
6721
+ Form: n$39,
6568
6722
  FormControl: g$4,
6569
- Heading: n$42,
6723
+ Heading: n$43,
6570
6724
  Image: n$35,
6571
- LayoutContainer: n$36,
6572
- Link: n$41,
6725
+ LayoutContainer: n$37,
6726
+ Link: n$42,
6573
6727
  List: r$3()({
6574
6728
  type: "list",
6575
6729
  attributeValues: {
6576
- subType: e$2()(["ul", "ol"]),
6577
- source: e$2()(["none", "controlled"])
6730
+ subType: e$3()(["ul", "ol"]),
6731
+ source: e$3()(["none", "controlled"])
6578
6732
  },
6579
6733
  sourceType: "list",
6580
6734
  content: {
@@ -6803,14 +6957,14 @@ var B$2 = {
6803
6957
  },
6804
6958
  initialItems: ["container"]
6805
6959
  }),
6806
- NodeHtml: t$56,
6960
+ NodeHtml: t$57,
6807
6961
  NotFound: t$49,
6808
- Page: t$48,
6962
+ Page: r$31,
6809
6963
  Pagination: r$3()({
6810
6964
  type: "pagination",
6811
6965
  attributeValues: {
6812
- mode: e$2()(["pages", "loadMore"]),
6813
- target: e$2()(["url", "interaction"])
6966
+ mode: e$3()(["pages", "loadMore"]),
6967
+ target: e$3()(["url", "interaction"])
6814
6968
  },
6815
6969
  triggers: { onPageChange: {
6816
6970
  action: "onPageChange",
@@ -6857,8 +7011,9 @@ var B$2 = {
6857
7011
  }
6858
7012
  }),
6859
7013
  Paragraph: t$52,
7014
+ PlitziSdk: t$56,
6860
7015
  Reference: t$55,
6861
- RichText: n$40,
7016
+ RichText: n$41,
6862
7017
  Svg: t$45,
6863
7018
  TabContainer: r$3()({
6864
7019
  type: "tabContainer",
@@ -6970,12 +7125,12 @@ var B$2 = {
6970
7125
  }
6971
7126
  }),
6972
7127
  Text: t$51,
6973
- ThemeToggle: r$32,
7128
+ ThemeToggle: r$33,
6974
7129
  Video: t$44
6975
7130
  };
6976
- Object.fromEntries(Object.values(B$2).flatMap((e) => "serverTemplates" in e ? [[e.type, e.serverTemplates]] : []));
7131
+ Object.fromEntries(Object.values(V$2).flatMap((e) => "serverTemplates" in e ? [[e.type, e.serverTemplates]] : []));
6977
7132
  /** Every declaration, read through the part of its shape this catalog uses. */
6978
- var declarations$1 = B$2;
7133
+ var declarations$1 = V$2;
6979
7134
  /** Keyed by the schema `type`, which is what a document and every catalog address a type by. */
6980
7135
  var elementCatalog = Object.fromEntries(Object.values(declarations$1).map((declaration) => [declaration.type, {
6981
7136
  label: declaration.content?.definition?.label ?? declaration.type,
@@ -8356,6 +8511,11 @@ Object.values(t$30).map((e) => ({
8356
8511
  }));
8357
8512
  t$30.DISPLAY, t$30.FLEX_WRAP, t$30.FLEX_DIRECTION, t$30.ALIGN_ITEMS, t$30.JUSTIFY_ITEMS, t$30.JUSTIFY_CONTENT, t$30.ALIGN_CONTENT, t$30.ROW_GAP, t$30.COLUMN_GAP, t$30.GRID_ROW_GAP, t$30.GRID_COLUMN_GAP, t$30.GRID_TEMPLATE_AREAS, t$30.GRID_TEMPLATE_COLUMNS, t$30.GRID_TEMPLATE_ROWS, t$30.GRID_AUTO_FLOW, t$30.GRID_AUTO_ROWS, t$30.GRID_AUTO_COLUMNS, t$30.GRID_ROW_START, t$30.GRID_COLUMN_START, t$30.GRID_ROW_END, t$30.GRID_COLUMN_END, t$30.ALIGN_SELF, t$30.JUSTIFY_SELF, t$30.ORDER, t$30.FLEX_GROW, t$30.FLEX_SHRINK, t$30.FLEX_BASIS, t$30.LIST_STYLE, t$30.LIST_STYLE_TYPE, t$30.LIST_STYLE_POSITION, t$30.LIST_STYLE_IMAGE, t$30.BACKGROUND_COLOR, t$30.BACKGROUND_IMAGE, t$30.BACKGROUND_POSITION, t$30.BACKGROUND_SIZE, t$30.BACKGROUND_CLIP, t$30.BACKGROUND_ORIGIN, t$30.BACKGROUND_REPEAT, t$30.BACKGROUND_ATTACHMENT, t$30.BACKGROUND_BLEND_MODE, t$30.MASK_IMAGE, t$30.MASK_SIZE, t$30.MASK_POSITION, t$30.MASK_REPEAT, t$30.MASK_COMPOSITE, t$30.WEBKIT_MASK_IMAGE, t$30.BORDER_TOP_STYLE, t$30.BORDER_TOP_WIDTH, t$30.BORDER_TOP_COLOR, t$30.BORDER_BOTTOM_STYLE, t$30.BORDER_BOTTOM_WIDTH, t$30.BORDER_BOTTOM_COLOR, t$30.BORDER_LEFT_STYLE, t$30.BORDER_LEFT_WIDTH, t$30.BORDER_LEFT_COLOR, t$30.BORDER_RIGHT_STYLE, t$30.BORDER_RIGHT_WIDTH, t$30.BORDER_RIGHT_COLOR, t$30.BORDER_TOP_LEFT_RADIUS, t$30.BORDER_TOP_RIGHT_RADIUS, t$30.BORDER_BOTTOM_LEFT_RADIUS, t$30.BORDER_BOTTOM_RIGHT_RADIUS, t$30.BORDER_COLLAPSE, t$30.BORDER_SPACING, t$30.TABLE_LAYOUT, t$30.OPACITY, t$30.VISIBILITY, t$30.CURSOR, t$30.TRANSITION, t$30.TRANSITION_PROPERTY, t$30.TRANSITION_DURATION, t$30.TRANSITION_TIMING_FUNCTION, t$30.TRANSITION_DELAY, t$30.TRANSITION_BEHAVIOR, t$30.ANIMATION, t$30.ANIMATION_NAME, t$30.ANIMATION_DURATION, t$30.ANIMATION_TIMING_FUNCTION, t$30.ANIMATION_DELAY, t$30.ANIMATION_ITERATION_COUNT, t$30.ANIMATION_DIRECTION, t$30.ANIMATION_FILL_MODE, t$30.ANIMATION_PLAY_STATE, t$30.ANIMATION_TIMELINE, t$30.ANIMATION_RANGE, t$30.BOX_SHADOW, t$30.FILTER, t$30.BACKDROP_FILTER, t$30.MIX_BLEND_MODE, t$30.ISOLATION, t$30.CLIP_PATH, t$30.TRANSFORM, t$30.TRANSFORM_ORIGIN, t$30.TRANSLATE, t$30.ROTATE, t$30.SCALE, t$30.TRANSFORM_STYLE, t$30.PERSPECTIVE, t$30.PERSPECTIVE_ORIGIN, t$30.BACKFACE_VISIBILITY, t$30.WILL_CHANGE, t$30.OUTLINE_WIDTH, t$30.OUTLINE_STYLE, t$30.OUTLINE_COLOR, t$30.OUTLINE_OFFSET, t$30.POINTER_EVENTS, t$30.USER_SELECT, t$30.TOUCH_ACTION, t$30.RESIZE, t$30.APPEARANCE, t$30.SCROLL_BEHAVIOR, t$30.OVERSCROLL_BEHAVIOR, t$30.SCROLL_SNAP_TYPE, t$30.SCROLL_SNAP_ALIGN, t$30.SCROLL_SNAP_STOP, t$30.SCROLL_PADDING_TOP, t$30.SCROLL_MARGIN_TOP, t$30.ACCENT_COLOR, t$30.CARET_COLOR, t$30.COLOR_SCHEME, t$30.POSITION, t$30.FLOAT, t$30.CLEAR, t$30.Z_INDEX, t$30.TOP, t$30.BOTTOM, t$30.LEFT, t$30.RIGHT, t$30.BOX_SIZING, t$30.ASPECT_RATIO, t$30.WIDTH, t$30.HEIGHT, t$30.MIN_WIDTH, t$30.MIN_HEIGHT, t$30.MAX_WIDTH, t$30.MAX_HEIGHT, t$30.OVERFLOW, t$30.OVERFLOW_X, t$30.OVERFLOW_Y, t$30.SCROLLBAR_WIDTH, t$30.SCROLLBAR_COLOR, t$30.CONTAINER_TYPE, t$30.CONTENT, t$30.CONTAINER_NAME, t$30.OBJECT_FIT, t$30.OBJECT_POSITION, t$30.COLUMN_WIDTH, t$30.COLUMN_COUNT, t$30.MARGIN_TOP, t$30.MARGIN_BOTTOM, t$30.MARGIN_LEFT, t$30.MARGIN_RIGHT, t$30.PADDING_TOP, t$30.PADDING_BOTTOM, t$30.PADDING_LEFT, t$30.PADDING_RIGHT, t$30.FONT_FAMILY, t$30.FONT_WEIGHT, t$30.FONT_SIZE, t$30.LINE_HEIGHT, t$30.COLOR, t$30.TEXT_ALIGN, t$30.FONT_STYLE, t$30.FONT_VARIANT, t$30.FONT_VARIANT_NUMERIC, t$30.FONT_FEATURE_SETTINGS, t$30.TEXT_DECORATION, t$30.TEXT_DECORATION_LINE, t$30.TEXT_DECORATION_COLOR, t$30.TEXT_DECORATION_STYLE, t$30.TEXT_DECORATION_THICKNESS, t$30.TEXT_UNDERLINE_OFFSET, t$30.LETTER_SPACING, t$30.WORD_SPACING, t$30.TEXT_INDENT, t$30.TEXT_TRANSFORM, t$30.DIRECTION, t$30.WRITING_MODE, t$30.TEXT_ORIENTATION, t$30.WHITE_SPACE, t$30.TEXT_WRAP, t$30.WORD_BREAK, t$30.OVERFLOW_WRAP, t$30.HYPHENS, t$30.VERTICAL_ALIGN, t$30.TEXT_OVERFLOW, t$30.TEXT_SHADOW, t$30.LINE_CLAMP, t$30.WEBKIT_LINE_CLAMP, t$30.WEBKIT_BOX_ORIENT, t$30.WEBKIT_TEXT_FILL_COLOR, t$30.WEBKIT_BACKGROUND_CLIP, t$30.BOX_DECORATION_BREAK, t$30.WEBKIT_BOX_DECORATION_BREAK, t$30.FILL, t$30.STROKE, t$30.STROKE_WIDTH;
8358
8513
  [...e$39];
8514
+ /**
8515
+ * The attribute a binding writes, from where it lands: `offlineData.schema` writes part of `offlineData` — the runtime
8516
+ * sets the path — so it is `offlineData` an element has to read.
8517
+ */
8518
+ var boundAttribute = (to) => to.split(".", 1)[0];
8359
8519
  /** Both binding forms as one list. The map form targets attributes, which is what nearly every binding does. */
8360
8520
  var toBindingSpecs = (bind) => Array.isArray(bind) ? bind : Object.entries(bind).map(([to, source]) => ({
8361
8521
  to,
@@ -8564,7 +8724,7 @@ var callFactory = (type, declaration, first, second) => {
8564
8724
  * the caller says they are, and nothing is claimed that is not known.
8565
8725
  */
8566
8726
  var defineElement = (declaration) => (first, second) => callFactory(declaration.type, declaration, first, second);
8567
- var declarationsByType = new Map(Object.values(B$2).map((declaration) => [declaration.type, declaration]));
8727
+ var declarationsByType = new Map(Object.values(V$2).map((declaration) => [declaration.type, declaration]));
8568
8728
  var elements_exports = /* @__PURE__ */ __exportAll({
8569
8729
  apiContainer: () => apiContainer,
8570
8730
  blockHtml: () => blockHtml,
@@ -8592,6 +8752,7 @@ var elements_exports = /* @__PURE__ */ __exportAll({
8592
8752
  nodeHtml: () => nodeHtml,
8593
8753
  pagination: () => pagination,
8594
8754
  paragraph: () => paragraph,
8755
+ plitziSdk: () => plitziSdk,
8595
8756
  reference: () => reference,
8596
8757
  richText: () => richText,
8597
8758
  svg: () => svg,
@@ -8617,30 +8778,30 @@ var elements_exports = /* @__PURE__ */ __exportAll({
8617
8778
  * appear in real documents, and their components are attached to their parent rather than exported on their own,
8618
8779
  * which is why their declarations had to be written before they could be authored at all.
8619
8780
  */
8620
- var apiContainer = defineElement(B$2.ApiContainer);
8621
- var blockHtml = defineElement(B$2.BlockHtml);
8622
- var blockJsx = defineElement(B$2.BlockJsx);
8623
- var button$1 = defineElement(B$2.Button);
8781
+ var apiContainer = defineElement(V$2.ApiContainer);
8782
+ var blockHtml = defineElement(V$2.BlockHtml);
8783
+ var blockJsx = defineElement(V$2.BlockJsx);
8784
+ var button$1 = defineElement(V$2.Button);
8624
8785
  /**
8625
8786
  * A carousel's `items` may name the source its slides come from, as a list's may: `carousel({ id: 'hero', items:
8626
8787
  * 'site.data.slides', row: r => … })` writes the slide into a `carouselTrack`, and its `children` are the controls.
8627
8788
  */
8628
- var carousel = defineElement(B$2.Carousel);
8629
- var carouselTrack = defineElement(B$2.CarouselTrack);
8630
- var channel = defineElement(B$2.Channel);
8631
- var container = defineElement(B$2.Container);
8632
- var custom$1 = defineElement(B$2.Custom);
8633
- var dialogContainer = defineElement(B$2.DialogContainer);
8634
- var dropdown = defineElement(B$2.Dropdown);
8635
- var dropdownPopup = defineElement(B$2.DropdownPopup);
8636
- var embed = defineElement(B$2.Embed);
8637
- var fontAwesome = defineElement(B$2.FontAwesome);
8638
- var form = defineElement(B$2.Form);
8639
- var formControl = defineElement(B$2.FormControl);
8640
- var heading = defineElement(B$2.Heading);
8641
- var image = defineElement(B$2.Image);
8642
- var link = defineElement(B$2.Link);
8643
- var listFactory = defineElement(B$2.List);
8789
+ var carousel = defineElement(V$2.Carousel);
8790
+ var carouselTrack = defineElement(V$2.CarouselTrack);
8791
+ var channel = defineElement(V$2.Channel);
8792
+ var container = defineElement(V$2.Container);
8793
+ var custom$1 = defineElement(V$2.Custom);
8794
+ var dialogContainer = defineElement(V$2.DialogContainer);
8795
+ var dropdown = defineElement(V$2.Dropdown);
8796
+ var dropdownPopup = defineElement(V$2.DropdownPopup);
8797
+ var embed = defineElement(V$2.Embed);
8798
+ var fontAwesome = defineElement(V$2.FontAwesome);
8799
+ var form = defineElement(V$2.Form);
8800
+ var formControl = defineElement(V$2.FormControl);
8801
+ var heading = defineElement(V$2.Heading);
8802
+ var image = defineElement(V$2.Image);
8803
+ var link = defineElement(V$2.Link);
8804
+ var listFactory = defineElement(V$2.List);
8644
8805
  var isTypedList = (props) => isSourcePath(props.items);
8645
8806
  function list$1(first, second) {
8646
8807
  if (Array.isArray(first)) return listFactory(first, second);
@@ -8651,22 +8812,24 @@ function list$1(first, second) {
8651
8812
  row: typeof row === "function" ? (named) => row(sourceRow(named.source, items)) : row
8652
8813
  });
8653
8814
  }
8654
- var listItem = defineElement(B$2.ListItem);
8655
- var markdown = defineElement(B$2.Markdown);
8656
- var modalContainer = defineElement(B$2.ModalContainer);
8657
- var nodeHtml = defineElement(B$2.NodeHtml);
8658
- var pagination = defineElement(B$2.Pagination);
8659
- var paragraph = defineElement(B$2.Paragraph);
8660
- var reference = defineElement(B$2.Reference);
8661
- var richText = defineElement(B$2.RichText);
8662
- var svg = defineElement(B$2.Svg);
8663
- var tabContainer = defineElement(B$2.TabContainer);
8664
- var tabContainerBody = defineElement(B$2.TabContainerBody);
8665
- var tabContainerHeader = defineElement(B$2.TabContainerHeader);
8666
- var tabContainerItem = defineElement(B$2.TabContainerItem);
8667
- var text$1 = defineElement(B$2.Text);
8668
- var themeToggle = defineElement(B$2.ThemeToggle);
8669
- var video = defineElement(B$2.Video);
8815
+ var listItem = defineElement(V$2.ListItem);
8816
+ var markdown = defineElement(V$2.Markdown);
8817
+ var modalContainer = defineElement(V$2.ModalContainer);
8818
+ var nodeHtml = defineElement(V$2.NodeHtml);
8819
+ var pagination = defineElement(V$2.Pagination);
8820
+ var paragraph = defineElement(V$2.Paragraph);
8821
+ /** Another space drawn inside this one, as a widget: `plitziSdk({ id: 'card', bind: { offlineData: 'view.data' } })`. */
8822
+ var plitziSdk = defineElement(V$2.PlitziSdk);
8823
+ var reference = defineElement(V$2.Reference);
8824
+ var richText = defineElement(V$2.RichText);
8825
+ var svg = defineElement(V$2.Svg);
8826
+ var tabContainer = defineElement(V$2.TabContainer);
8827
+ var tabContainerBody = defineElement(V$2.TabContainerBody);
8828
+ var tabContainerHeader = defineElement(V$2.TabContainerHeader);
8829
+ var tabContainerItem = defineElement(V$2.TabContainerItem);
8830
+ var text$1 = defineElement(V$2.Text);
8831
+ var themeToggle = defineElement(V$2.ThemeToggle);
8832
+ var video = defineElement(V$2.Video);
8670
8833
  var e$33 = (e) => typeof e == "object" && !!e && !Array.isArray(e);
8671
8834
  var STEP_TYPES = [
8672
8835
  "trigger",
@@ -12893,7 +13056,7 @@ new Set(Object.keys({
12893
13056
  const names = ctx.attributeNamesFor(element);
12894
13057
  const bindings = element.definition.bindings;
12895
13058
  if (!names || !bindings?.attributes) return;
12896
- const reads = (binding) => binding.to === "visibility" || binding.to === "className" || names.includes(binding.to);
13059
+ const reads = (binding) => binding.to === "visibility" || binding.to === "className" || names.includes(boundAttribute(binding.to));
12897
13060
  for (const binding of bindings.attributes.filter((candidate) => !reads(candidate))) report(`Removed the binding onto "${binding.to}", which a "${element.definition.type}" never reads.`, {
12898
13061
  on: "binding",
12899
13062
  op: "remove",
@@ -14651,7 +14814,7 @@ var placementOf = ({ name, declaration, attributes }, id = declaration.type) =>
14651
14814
  var pluginText = (plugin) => {
14652
14815
  const { declaration } = plugin;
14653
14816
  const definition = declaration.content?.definition;
14654
- const description = e$5(definition) && typeof definition.description === "string" ? definition.description : "";
14817
+ const description = e$6(definition) && typeof definition.description === "string" ? definition.description : "";
14655
14818
  const attributes = Object.entries(declaration.content?.attributes ?? {});
14656
14819
  const triggers = Object.keys(declaration.triggers ?? {});
14657
14820
  const callbacks = Object.keys(declaration.callbacks ?? {});
@@ -14673,13 +14836,13 @@ var pluginText = (plugin) => {
14673
14836
  var requireServerTypes = async (root) => {
14674
14837
  const file = path.join(root, "package.json");
14675
14838
  const json = JSON.parse(await fs.readFile(file, "utf-8"));
14676
- if (!e$5(json)) return;
14839
+ if (!e$6(json)) return;
14677
14840
  if ([
14678
14841
  json.dependencies,
14679
14842
  json.devDependencies,
14680
14843
  json.peerDependencies
14681
- ].some((list) => e$5(list) && "@plitzi/sdk-server" in list)) return;
14682
- const devDependencies = e$5(json.devDependencies) ? json.devDependencies : {};
14844
+ ].some((list) => e$6(list) && "@plitzi/sdk-server" in list)) return;
14845
+ const devDependencies = e$6(json.devDependencies) ? json.devDependencies : {};
14683
14846
  json.devDependencies = Object.fromEntries(Object.entries({
14684
14847
  ...devDependencies,
14685
14848
  "@plitzi/sdk-server": SDK_VERSION
@@ -14923,7 +15086,7 @@ var addPlugin = async (namesGiven, options) => {
14923
15086
  * carry it — is not refused anything: every file that differs is then shown as the project's, for its author to take.
14924
15087
  */
14925
15088
  var SCAFFOLD_RECORD_FILE = path.join(".plitzi", "scaffold.json");
14926
- var stringRecord = (value) => e$5(value) ? Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string")) : {};
15089
+ var stringRecord = (value) => e$6(value) ? Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string")) : {};
14927
15090
  var isPackageManager = (value) => PACKAGE_MANAGERS.some((manager) => manager === value);
14928
15091
  var digestOf = (contents) => createHash("sha256").update(contents).digest("hex");
14929
15092
  var readScaffoldRecord = async (root) => {
@@ -14933,12 +15096,12 @@ var readScaffoldRecord = async (root) => {
14933
15096
  } catch {
14934
15097
  return;
14935
15098
  }
14936
- if (!e$5(parsed) || parsed.format !== 1 || typeof parsed.cli !== "string" || !e$5(parsed.files)) return;
15099
+ if (!e$6(parsed) || parsed.format !== 1 || typeof parsed.cli !== "string" || !e$6(parsed.files)) return;
14937
15100
  return {
14938
15101
  format: 1,
14939
15102
  cli: parsed.cli,
14940
15103
  files: stringRecord(parsed.files),
14941
- ...e$5(parsed.scripts) ? { scripts: stringRecord(parsed.scripts) } : {},
15104
+ ...e$6(parsed.scripts) ? { scripts: stringRecord(parsed.scripts) } : {},
14942
15105
  ...isPackageManager(parsed.packageManager) ? { packageManager: parsed.packageManager } : {}
14943
15106
  };
14944
15107
  };
@@ -14946,7 +15109,7 @@ var readScaffoldRecord = async (root) => {
14946
15109
  var scriptsOf$1 = (packageJsonText) => {
14947
15110
  try {
14948
15111
  const parsed = JSON.parse(packageJsonText ?? "");
14949
- return e$5(parsed) ? stringRecord(parsed.scripts) : {};
15112
+ return e$6(parsed) ? stringRecord(parsed.scripts) : {};
14950
15113
  } catch {
14951
15114
  return {};
14952
15115
  }
@@ -15243,7 +15406,7 @@ var projectFromSpace = (exported, spaceSource) => {
15243
15406
  * copy of the whole project beside the project would be the second source of it nobody keeps in step.
15244
15407
  */
15245
15408
  var ORIGIN_FILE = path.join(".plitzi", "space.json");
15246
- var isStrings$1 = (value) => e$5(value) && Object.values(value).every((entry) => typeof entry === "string");
15409
+ var isStrings$1 = (value) => e$6(value) && Object.values(value).every((entry) => typeof entry === "string");
15247
15410
  var digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
15248
15411
  /**
15249
15412
  * A file of the project as a pull compares it: the digest of what the project's formatter makes of it — so a file only
@@ -15267,7 +15430,7 @@ var digestsOf = async (root, file, format) => {
15267
15430
  var readOrigin = async (root) => {
15268
15431
  try {
15269
15432
  const value = JSON.parse(await fs.readFile(path.join(root, ORIGIN_FILE), "utf-8"));
15270
- if (!e$5(value) || value.format !== 1 || typeof value.api !== "string" || !e$5(value.space) || typeof value.space.id !== "number" || typeof value.space.name !== "string" || typeof value.space.permanentUrl !== "string" || value.source !== "local" && value.source !== "cloud" || !e$5(value.version) || typeof value.version.environment !== "string" || value.version.revision !== void 0 && typeof value.version.revision !== "number" || value.draft !== void 0 && typeof value.draft !== "string" || !isStrings$1(value.files) || !isStrings$1(value.downloads) || !isStrings$1(value.dependencies) || value.data !== void 0 && typeof value.data !== "string") return;
15433
+ if (!e$6(value) || value.format !== 1 || typeof value.api !== "string" || !e$6(value.space) || typeof value.space.id !== "number" || typeof value.space.name !== "string" || typeof value.space.permanentUrl !== "string" || value.source !== "local" && value.source !== "cloud" || !e$6(value.version) || typeof value.version.environment !== "string" || value.version.revision !== void 0 && typeof value.version.revision !== "number" || value.draft !== void 0 && typeof value.draft !== "string" || !isStrings$1(value.files) || !isStrings$1(value.downloads) || !isStrings$1(value.dependencies) || value.data !== void 0 && typeof value.data !== "string") return;
15271
15434
  return {
15272
15435
  format: 1,
15273
15436
  api: value.api,
@@ -15415,12 +15578,12 @@ var SCOPED_NAME = /(@plitzi\/[\w.-]+?)(?:@[^/]*)?$/;
15415
15578
  var readJson = async (file) => {
15416
15579
  try {
15417
15580
  const parsed = JSON.parse(await fs.readFile(file, "utf-8"));
15418
- return e$5(parsed) ? parsed : void 0;
15581
+ return e$6(parsed) ? parsed : void 0;
15419
15582
  } catch {
15420
15583
  return;
15421
15584
  }
15422
15585
  };
15423
- var stringsOf$2 = (value) => e$5(value) ? Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string")) : {};
15586
+ var stringsOf$2 = (value) => e$6(value) ? Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string")) : {};
15424
15587
  /** The folder Node finds the scope in from the project's root: the nearest `node_modules/@plitzi`, here or above. */
15425
15588
  var scopeFolder = async (root) => {
15426
15589
  for (let dir = root;; dir = path.dirname(dir)) {
@@ -15442,8 +15605,8 @@ var overridden = (manifest) => {
15442
15605
  return [
15443
15606
  ["overrides", manifest.overrides],
15444
15607
  ["resolutions", manifest.resolutions],
15445
- ["pnpm.overrides", e$5(manifest.pnpm) ? manifest.pnpm.overrides : void 0]
15446
- ].flatMap(([field, value]) => Object.entries(e$5(value) ? value : {}).flatMap(([key, to]) => {
15608
+ ["pnpm.overrides", e$6(manifest.pnpm) ? manifest.pnpm.overrides : void 0]
15609
+ ].flatMap(([field, value]) => Object.entries(e$6(value) ? value : {}).flatMap(([key, to]) => {
15447
15610
  const name = SCOPED_NAME.exec(key)?.[1];
15448
15611
  return name ? [{
15449
15612
  name,
@@ -15463,9 +15626,9 @@ var linked = async (root, scope) => {
15463
15626
  }));
15464
15627
  };
15465
15628
  /** One package of the scope as npm recorded it: what is installed (`node_modules/.package-lock.json`), or saved. */
15466
- var npmEntries = (lock) => new Map(Object.entries(e$5(lock?.packages) ? lock.packages : {}).flatMap(([key, entry]) => {
15629
+ var npmEntries = (lock) => new Map(Object.entries(e$6(lock?.packages) ? lock.packages : {}).flatMap(([key, entry]) => {
15467
15630
  const name = key.startsWith(`node_modules/${SCOPE$1}/`) ? key.slice(13) : void 0;
15468
- if (!name || name.includes("/node_modules/") || !e$5(entry)) return [];
15631
+ if (!name || name.includes("/node_modules/") || !e$6(entry)) return [];
15469
15632
  return [[name, {
15470
15633
  ...typeof entry.version === "string" ? { version: entry.version } : {},
15471
15634
  ...typeof entry.resolved === "string" ? { resolved: entry.resolved } : {}
@@ -15591,23 +15754,36 @@ var UPGRADE_PARTS = [
15591
15754
  "skills",
15592
15755
  "renames"
15593
15756
  ];
15594
- var RENAMES = [{
15595
- name: "canTemplate",
15596
- to: "canSnippet",
15597
- since: "0.38.0",
15598
- what: "a plugin’s builder config key"
15599
- }, ...[
15600
- "authorTemplate",
15601
- "validateTemplate",
15602
- "TemplateSpec",
15603
- "AuthoredTemplate"
15604
- ].map((name) => ({
15605
- name,
15606
- to: name.replace("Template", "Snippet"),
15607
- since: "0.38.0",
15608
- what: "what the builder saves from a subtree is a snippet",
15609
- from: "@plitzi/sdk-authoring"
15610
- }))];
15757
+ var RENAMES = [
15758
+ {
15759
+ name: "canTemplate",
15760
+ to: "canSnippet",
15761
+ since: "0.38.0",
15762
+ what: "a plugin’s builder config key"
15763
+ },
15764
+ ...[
15765
+ "authorTemplate",
15766
+ "validateTemplate",
15767
+ "TemplateSpec",
15768
+ "AuthoredTemplate"
15769
+ ].map((name) => ({
15770
+ name,
15771
+ to: name.replace("Template", "Snippet"),
15772
+ since: "0.38.0",
15773
+ what: "what the builder saves from a subtree is a snippet",
15774
+ from: "@plitzi/sdk-authoring"
15775
+ })),
15776
+ ...[
15777
+ ["usePlitziServiceContext", "usePlitzi"],
15778
+ ["PlitziServiceProvider", "PlitziProvider"],
15779
+ ["PlitziServiceContextValue", "PlitziContextValue"]
15780
+ ].map(([name, to]) => ({
15781
+ name,
15782
+ to,
15783
+ since: "0.38.12",
15784
+ what: "what the host rendering a space tells its elements: `usePlitzi()`"
15785
+ }))
15786
+ ];
15611
15787
  /** The project's own source: TypeScript and JavaScript under `src/` and `visual/`, never what was installed or built. */
15612
15788
  var sourceFiles = async (root) => {
15613
15789
  const found = [];
@@ -15685,7 +15861,7 @@ var isBelow = (range, than) => {
15685
15861
  const at = a.findIndex((part, index) => part !== b[index]);
15686
15862
  return at !== -1 && a[at] < b[at];
15687
15863
  };
15688
- var stringsOf$1 = (value) => e$5(value) ? Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string")) : {};
15864
+ var stringsOf$1 = (value) => e$6(value) ? Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string")) : {};
15689
15865
  /**
15690
15866
  * `package.json` brought up to the CLI. `wrote` is what the CLI wrote of its scripts (the scaffold record): one the
15691
15867
  * project still has as written is the CLI's and takes today's command; one it changed is its own and is only said.
@@ -15694,8 +15870,8 @@ var stringsOf$1 = (value) => e$5(value) ? Object.fromEntries(Object.entries(valu
15694
15870
  var planPackages = (text, answers, wrote = {}, local = /* @__PURE__ */ new Set()) => {
15695
15871
  const parsed = JSON.parse(text);
15696
15872
  const ours = JSON.parse(packageJson$1(answers));
15697
- const project = e$5(parsed) ? { ...parsed } : {};
15698
- const scaffold = e$5(ours) ? ours : {};
15873
+ const project = e$6(parsed) ? { ...parsed } : {};
15874
+ const scaffold = e$6(ours) ? ours : {};
15699
15875
  const plan = {
15700
15876
  added: [],
15701
15877
  raised: [],
@@ -15858,6 +16034,30 @@ var machineryPlan = async (root, answers, { origin, recorded, take = [] }) => {
15858
16034
  spaces: machinery.filter(([file]) => given.has(file)).map(([file]) => file)
15859
16035
  };
15860
16036
  };
16037
+ /** Every file of a skill as it is in the project, by its path from the root. */
16038
+ var skillOnDisk = async (root, name) => {
16039
+ const folder = path.join(".claude/skills", name);
16040
+ const entries = await fs.readdir(path.join(root, folder), {
16041
+ recursive: true,
16042
+ withFileTypes: true
16043
+ }).catch(() => []);
16044
+ const found = {};
16045
+ for (const entry of entries.filter((candidate) => candidate.isFile())) {
16046
+ const file = path.relative(root, path.join(entry.parentPath, entry.name));
16047
+ found[file] = await readText$1(path.join(root, file)) ?? "";
16048
+ }
16049
+ return found;
16050
+ };
16051
+ /**
16052
+ * Whether any file of a skill is not what the installed package has — one added, gone or changed. `SKILL.md` alone
16053
+ * missed a change made only in a reference, which a build of the same version makes.
16054
+ */
16055
+ var skillFilesDiffer = async (root, name, files) => {
16056
+ const prefix = `.claude/skills/${name}/`;
16057
+ const wanted = Object.entries(files).filter(([file]) => file.startsWith(prefix));
16058
+ const present = await skillOnDisk(root, name);
16059
+ return wanted.length !== Object.keys(present).length || wanted.some(([file, text]) => present[file.split("/").join(path.sep)] !== text);
16060
+ };
15861
16061
  var planSkills = async (root) => {
15862
16062
  const files = skillFiles(SKILL_NAMES, root);
15863
16063
  const plans = [];
@@ -15866,7 +16066,7 @@ var planSkills = async (root) => {
15866
16066
  if (!(skill in files)) continue;
15867
16067
  const now = files[skill];
15868
16068
  const was = await readText$1(path.join(root, skill));
15869
- if (was !== now) plans.push({
16069
+ if (was !== now || await skillFilesDiffer(root, name, files)) plans.push({
15870
16070
  name,
15871
16071
  ...was === void 0 ? {} : { was: skillVersion(was) ?? "unversioned" },
15872
16072
  now: skillVersion(now) ?? "unversioned"
@@ -16022,7 +16222,7 @@ var upgrade = async (parts, options) => {
16022
16222
  if (after !== void 0) {
16023
16223
  installed = "needed";
16024
16224
  if (write) {
16025
- await fs.writeFile(path.join(root, "package.json"), after);
16225
+ await fs.writeFile(path.join(root, "package.json"), await (await projectFormatter(root))("package.json", after));
16026
16226
  if (local.length > 0) installed = "skipped";
16027
16227
  else if (options.install !== false && !options.json) installed = await install(manager, root) ? "done" : "failed";
16028
16228
  }
@@ -16086,38 +16286,47 @@ export default defineRuntime({
16086
16286
  })
16087
16287
  });
16088
16288
  `;
16089
- /** `start:dev` brought to restart on the runtime too — when it is the CLI's; one the project changed is said, not rewritten. */
16090
- var planStartDev = async (root, answers) => {
16091
- const command = projectScripts({
16289
+ /** The scripts that restart the server on its own code: a runtime is one folder more each of them watches. */
16290
+ var WATCHING = ["start:dev", "start:dev-inspect"];
16291
+ /** The watching scripts brought to restart on the runtime too — those that are the CLI's; one the project changed is said. */
16292
+ var planWatching = async (root, answers) => {
16293
+ const withRuntime = projectScripts({
16092
16294
  ...answers,
16093
16295
  runtime: true
16094
- })["start:dev"];
16095
- const text = await fs.readFile(path.join(root, "package.json"), "utf-8");
16096
- const manifest = JSON.parse(text);
16097
- const scripts = e$5(manifest) && e$5(manifest.scripts) ? manifest.scripts : {};
16098
- const current = scripts["start:dev"];
16099
- if (current === command) return { command };
16100
- const recorded = (await readScaffoldRecord(root))?.scripts?.["start:dev"];
16101
- const theCli = current === projectScripts({
16296
+ });
16297
+ const without = projectScripts({
16102
16298
  ...answers,
16103
16299
  runtime: false
16104
- })["start:dev"] || current === recorded;
16105
- if (typeof current === "string" && theCli) {
16106
- const next = {
16107
- ...e$5(manifest) ? manifest : {},
16108
- scripts: {
16109
- ...scripts,
16110
- "start:dev": command
16111
- }
16112
- };
16113
- return {
16114
- command,
16115
- script: `${JSON.stringify(next, null, 2)}\n`
16116
- };
16300
+ });
16301
+ const recorded = (await readScaffoldRecord(root))?.scripts ?? {};
16302
+ const manifest = JSON.parse(await fs.readFile(path.join(root, "package.json"), "utf-8"));
16303
+ const scripts = e$6(manifest) && e$6(manifest.scripts) ? manifest.scripts : {};
16304
+ const changed = {};
16305
+ const yours = [];
16306
+ for (const name of WATCHING) {
16307
+ const current = scripts[name];
16308
+ if (typeof current !== "string" || current === withRuntime[name]) continue;
16309
+ if (current === without[name] || current === recorded[name]) changed[name] = withRuntime[name];
16310
+ else yours.push({
16311
+ name,
16312
+ command: current
16313
+ });
16117
16314
  }
16315
+ if (Object.keys(changed).length === 0) return {
16316
+ changed,
16317
+ yours
16318
+ };
16319
+ const next = {
16320
+ ...e$6(manifest) ? manifest : {},
16321
+ scripts: {
16322
+ ...scripts,
16323
+ ...changed
16324
+ }
16325
+ };
16118
16326
  return {
16119
- command,
16120
- yours: typeof current === "string" ? current : ""
16327
+ changed,
16328
+ script: `${JSON.stringify(next, null, 2)}\n`,
16329
+ yours
16121
16330
  };
16122
16331
  };
16123
16332
  var addRuntime = async (options) => {
@@ -16138,25 +16347,25 @@ var addRuntime = async (options) => {
16138
16347
  }
16139
16348
  const answers = await answersFor(project.root, plitzi, project.packageManager ?? "npm", await readOrigin(project.root) !== void 0);
16140
16349
  const module = await (await projectFormatter(project.root))(_$1, RUNTIME_MODULE);
16141
- const startDev = await planStartDev(project.root, answers);
16350
+ const watching = await planWatching(project.root, answers);
16142
16351
  if (options.dryRun) {
16143
- sayDryRun("plitzi runtime add", [...await filesWouldWrite(project.root, [_$1]), ...startDev.script ? ["~ package.json — start:dev restarts on src/runtime/ too"] : []]);
16352
+ sayDryRun("plitzi runtime add", [...await filesWouldWrite(project.root, [_$1]), ...watching.script ? [`~ package.json — ${WATCHING.join(" and ")} restart on ${g$2}/ too`] : []]);
16144
16353
  return;
16145
16354
  }
16146
16355
  await writeFiles(project.root, {
16147
16356
  [_$1]: module,
16148
- ...startDev.script ? { "package.json": startDev.script } : {}
16357
+ ...watching.script ? { "package.json": watching.script } : {}
16149
16358
  });
16150
- if (startDev.script) {
16359
+ if (watching.script) {
16151
16360
  const recorded = (await readScaffoldRecord(project.root))?.scripts ?? {};
16152
16361
  await writeScaffoldRecord(project.root, CLI_VERSION, { scripts: {
16153
16362
  ...recorded,
16154
- "start:dev": startDev.command
16363
+ ...watching.changed
16155
16364
  } });
16156
16365
  }
16157
16366
  console.log(chalk.green(`\n${_$1} — the space's runtime`));
16158
16367
  console.log("\nThe server runs it — `/hello` answers on this project — and `plitzi runtime push` sends it to the space: the same module, tried here before it goes.");
16159
- if (startDev.yours !== void 0) console.log(chalk.yellow(`\nstart:dev is yours ("${startDev.yours}"): add --watch-path=./${g$2} to it, for a save to the runtime to restart the server.`));
16368
+ for (const { name, command } of watching.yours) console.log(chalk.yellow(`\n${name} is yours ("${command}"): add --watch-path=./${g$2} to it, for a save to the runtime to restart the server.`));
16160
16369
  console.log("");
16161
16370
  };
16162
16371
  //#endregion
@@ -16179,9 +16388,9 @@ var WINDOW_MS = 6e5;
16179
16388
  var read = async (root, now) => {
16180
16389
  try {
16181
16390
  const parsed = JSON.parse(await fs.readFile(path.join(root, FILE), "utf-8"));
16182
- if (!e$5(parsed)) return {};
16391
+ if (!e$6(parsed)) return {};
16183
16392
  const kept = {};
16184
- for (const [key, entry] of Object.entries(parsed)) if (e$5(entry) && typeof entry.count === "number" && typeof entry.at === "number" && now - entry.at < WINDOW_MS) kept[key] = {
16393
+ for (const [key, entry] of Object.entries(parsed)) if (e$6(entry) && typeof entry.count === "number" && typeof entry.at === "number" && now - entry.at < WINDOW_MS) kept[key] = {
16185
16394
  count: entry.count,
16186
16395
  at: entry.at
16187
16396
  };
@@ -16206,7 +16415,7 @@ var noteRefused = async (root, command, now = Date.now()) => {
16206
16415
  var AGAIN = "This is the same command refused again: running it once more will not change the answer. Change what it names, edit the call by hand (`plitzi element where` shows it), or ask the person what they meant.";
16207
16416
  //#endregion
16208
16417
  //#region ../../packages/sdk-shared/dist/helpers/canonicalJson.mjs
16209
- var t$1 = (t) => t === void 0 ? "undefined" : JSON.stringify(t, (t, n) => e$5(n) ? Object.fromEntries(Object.keys(n).toSorted().map((e) => [e, n[e]])) : n);
16418
+ var t$2 = (t) => t === void 0 ? "undefined" : JSON.stringify(t, (t, n) => e$6(n) ? Object.fromEntries(Object.keys(n).toSorted().map((e) => [e, n[e]])) : n);
16210
16419
  //#endregion
16211
16420
  //#region src/commands/projectSpace.ts
16212
16421
  /** What `projectSpaceAt` refuses with a message that is the whole report: the root, the layout, a module with no space. */
@@ -17256,9 +17465,9 @@ var readingOf = ({ elementId, type, rootId, classes, attributes, templates, boun
17256
17465
  });
17257
17466
  var isStrings = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
17258
17467
  /** What another process printed, read as the space when every entry is an element's reading. */
17259
- var isReadings = (value) => Array.isArray(value) && value.every((entry) => e$5(entry) && typeof entry.elementId === "string" && typeof entry.type === "string" && typeof entry.rootId === "string" && isStrings(entry.classes) && e$5(entry.attributes) && isStrings(entry.templates) && isStrings(entry.bound) && isStrings(entry.children));
17468
+ var isReadings = (value) => Array.isArray(value) && value.every((entry) => e$6(entry) && typeof entry.elementId === "string" && typeof entry.type === "string" && typeof entry.rootId === "string" && isStrings(entry.classes) && e$6(entry.attributes) && isStrings(entry.templates) && isStrings(entry.bound) && isStrings(entry.children));
17260
17469
  var shown = (value) => {
17261
- const text = t$1(value);
17470
+ const text = t$2(value);
17262
17471
  return text.length > SHOWN ? `${text.slice(0, 59)}…` : text;
17263
17472
  };
17264
17473
  var FIELDS$1 = [
@@ -17302,7 +17511,7 @@ var spaceEffects = (before, after) => {
17302
17511
  });
17303
17512
  continue;
17304
17513
  }
17305
- for (const field of FIELDS$1) if (t$1(old[field]) !== t$1(element[field])) effects.push({
17514
+ for (const field of FIELDS$1) if (t$2(old[field]) !== t$2(element[field])) effects.push({
17306
17515
  elementId,
17307
17516
  kind: "field",
17308
17517
  field,
@@ -17311,7 +17520,7 @@ var spaceEffects = (before, after) => {
17311
17520
  for (const key of /* @__PURE__ */ new Set([...Object.keys(old.attributes), ...Object.keys(element.attributes)])) {
17312
17521
  const from = old.attributes[key];
17313
17522
  const to = element.attributes[key];
17314
- if (t$1(from) === t$1(to)) continue;
17523
+ if (t$2(from) === t$2(to)) continue;
17315
17524
  const line = from === void 0 ? `${elementId}.${key} = ${shown(to)}` : to === void 0 ? `${elementId}.${key} removed (was ${shown(from)})` : `${elementId}.${key}: ${shown(from)} → ${shown(to)}`;
17316
17525
  effects.push({
17317
17526
  elementId,
@@ -17361,7 +17570,7 @@ var surprises = (effects, after, asked) => {
17361
17570
  const absent = [...asked].flatMap(([elementId, changes]) => {
17362
17571
  const element = readings.get(elementId);
17363
17572
  if (!element) return [`${elementId} is no longer in the space`];
17364
- return changes.filter((change) => change.value === void 0 ? change.key in element.attributes : t$1(element.attributes[change.key]) !== t$1(change.value)).map((change) => change.value === void 0 ? `${elementId}.${change.key} is still set` : `${elementId}.${change.key} reads ${shown(element.attributes[change.key])}, not ${shown(change.value)}`);
17573
+ return changes.filter((change) => change.value === void 0 ? change.key in element.attributes : t$2(element.attributes[change.key]) !== t$2(change.value)).map((change) => change.value === void 0 ? `${elementId}.${change.key} is still set` : `${elementId}.${change.key} reads ${shown(element.attributes[change.key])}, not ${shown(change.value)}`);
17365
17574
  });
17366
17575
  const unasked = effects.filter((effect) => effect.key === void 0 || !asked.get(effect.elementId)?.some((change) => change.key === effect.key)).map((effect) => `it changed ${effect.line} too, which was not asked`);
17367
17576
  return [...absent, ...unasked];
@@ -17376,18 +17585,18 @@ var reachedToo = (effects, after, asked, changes) => {
17376
17585
  return effects.flatMap(({ elementId, key }) => {
17377
17586
  const change = changes.find((candidate) => candidate.key === key && candidate.value !== void 0);
17378
17587
  const reads = readings.get(elementId)?.attributes[key ?? ""];
17379
- if (key === void 0 || !change || asked.get(elementId)?.some((known) => known.key === key) || t$1(reads) !== t$1(change.value)) return [];
17588
+ if (key === void 0 || !change || asked.get(elementId)?.some((known) => known.key === key) || t$2(reads) !== t$2(change.value)) return [];
17380
17589
  return [{
17381
17590
  elementId,
17382
17591
  change
17383
17592
  }];
17384
17593
  });
17385
17594
  };
17386
- var run$2 = promisify(execFile);
17595
+ var run$3 = promisify(execFile);
17387
17596
  /** The space as it authors now, read by a fresh process: this one loaded the files before they were edited. */
17388
17597
  var readAfresh = async () => {
17389
17598
  try {
17390
- const { stdout } = await run$2(process.execPath, [
17599
+ const { stdout } = await run$3(process.execPath, [
17391
17600
  process.argv[1],
17392
17601
  "element",
17393
17602
  "readings"
@@ -17397,9 +17606,9 @@ var readAfresh = async () => {
17397
17606
  });
17398
17607
  const parsed = JSON.parse(stdout);
17399
17608
  if (isReadings(parsed)) return parsed;
17400
- return { problem: e$5(parsed) && typeof parsed.problem === "string" ? parsed.problem : "it did not answer" };
17609
+ return { problem: e$6(parsed) && typeof parsed.problem === "string" ? parsed.problem : "it did not answer" };
17401
17610
  } catch (error) {
17402
- return { problem: (e$5(error) && typeof error.stdout === "string" ? error.stdout.trim() : "") || (error instanceof Error ? error.message.split("\n")[0] : String(error)) };
17611
+ return { problem: (e$6(error) && typeof error.stdout === "string" ? error.stdout.trim() : "") || (error instanceof Error ? error.message.split("\n")[0] : String(error)) };
17403
17612
  }
17404
17613
  };
17405
17614
  /**
@@ -17691,6 +17900,10 @@ var move$1 = async (elementId, options) => {
17691
17900
  ].join("\n"));
17692
17901
  };
17693
17902
  //#endregion
17903
+ //#region ../../packages/sdk-shared/dist/project/network.mjs
17904
+ var e$2 = (e) => Object.values(e).flat().flatMap((e) => e && e.family === "IPv4" && !e.internal ? [e.address] : []);
17905
+ var t$1 = (e) => `${(e.split(".").at(0) ?? "").toLowerCase()}.local`;
17906
+ //#endregion
17694
17907
  //#region src/commands/projectSettings.ts
17695
17908
  /**
17696
17909
  * What the project's processes start with: what its `.env` sets, under what the environment already has — as its
@@ -17704,6 +17917,106 @@ var projectSettings = async (root) => {
17704
17917
  value: (name) => process.env[name] ?? parsed[name] ?? ""
17705
17918
  };
17706
17919
  };
17920
+ /**
17921
+ * `.env`'s text with `name` set to `value`: its line replaced where it is — the commented one `.env.example` offers too,
17922
+ * `# HOST=0.0.0.0` — or added at the end. Every other line as it was, comments included.
17923
+ */
17924
+ var withSetting = (text, name, value) => {
17925
+ const line = `${name}=${value}`;
17926
+ const pattern = new RegExp(`^(?:#\\s*)?${name}=.*$`, "m");
17927
+ if (pattern.test(text)) return text.replace(pattern, line);
17928
+ return `${text}${text === "" || text.endsWith("\n") ? "" : "\n"}${line}\n`;
17929
+ };
17930
+ //#endregion
17931
+ //#region src/commands/cert.ts
17932
+ var run$2 = promisify(execFile);
17933
+ var LOOPBACK$1 = [
17934
+ "localhost",
17935
+ "127.0.0.1",
17936
+ "::1"
17937
+ ];
17938
+ /**
17939
+ * What the certificate names: this machine (loopback), its network addresses — what a phone opens today — its mDNS
17940
+ * name, which outlives the addresses, and any the person adds.
17941
+ */
17942
+ var certificateNames = (interfaces, machine, extra = []) => [.../* @__PURE__ */ new Set([
17943
+ ...LOOPBACK$1,
17944
+ ...e$2(interfaces),
17945
+ t$1(machine),
17946
+ ...extra
17947
+ ])];
17948
+ var INSTALL_MKCERT = {
17949
+ darwin: "brew install mkcert",
17950
+ linux: "apt install mkcert (or your distribution’s package; see https://github.com/FiloSottile/mkcert)",
17951
+ win32: "choco install mkcert, or scoop install mkcert"
17952
+ };
17953
+ /** mkcert's authority folder, or `undefined` when mkcert is not installed. */
17954
+ var authorityFolder = async () => {
17955
+ try {
17956
+ const { stdout } = await run$2("mkcert", ["-CAROOT"]);
17957
+ return stdout.trim();
17958
+ } catch {
17959
+ return;
17960
+ }
17961
+ };
17962
+ /** `mkcert -install`, at the terminal: it may ask for the password that lets it trust its authority on this machine. */
17963
+ var trustHere = () => new Promise((resolve) => {
17964
+ spawn("mkcert", ["-install"], { stdio: "inherit" }).on("error", () => resolve(false)).on("exit", (code) => resolve(code === 0));
17965
+ });
17966
+ var cert = async (options) => {
17967
+ const project = await projectHere("to serve over HTTPS");
17968
+ if (!project) return;
17969
+ const { root } = project;
17970
+ const authority = await authorityFolder();
17971
+ if (authority === void 0) {
17972
+ fail(`mkcert is not installed: it makes the certificate, and the authority your devices trust. Install it — ${INSTALL_MKCERT[process.platform] ?? "see https://github.com/FiloSottile/mkcert"} — and run plitzi cert again.`);
17973
+ return;
17974
+ }
17975
+ const trusted = atTerminal() && !options.json ? await trustHere() : false;
17976
+ const names = certificateNames(networkInterfaces(), hostname(), options.name);
17977
+ await fs.mkdir(path.join(root, path.dirname(TLS_CERT_FILE)), { recursive: true });
17978
+ try {
17979
+ await run$2("mkcert", [
17980
+ "-cert-file",
17981
+ TLS_CERT_FILE,
17982
+ "-key-file",
17983
+ TLS_KEY_FILE,
17984
+ ...names
17985
+ ], { cwd: root });
17986
+ } catch (error) {
17987
+ fail(`mkcert could not make the certificate: ${error instanceof Error ? error.message : String(error)}`);
17988
+ return;
17989
+ }
17990
+ const envFile = path.join(root, ".env");
17991
+ const env = await fs.readFile(envFile, "utf-8").catch(() => "");
17992
+ await fs.writeFile(envFile, withSetting(withSetting(env, "TLS_CERT", TLS_CERT_FILE), "TLS_KEY", TLS_KEY_FILE));
17993
+ const rootCa = path.join(authority, "rootCA.pem");
17994
+ const settings = await projectSettings(root);
17995
+ const open = settings.value("HOST") !== "" && !LOOPBACK$1.includes(settings.value("HOST"));
17996
+ const port = settings.value("PORT") || (project.plitzi?.kind === "project" && project.plitzi.mode === "client" ? "5173" : "8080");
17997
+ const urls = names.filter((name) => !LOOPBACK$1.includes(name)).map((name) => `https://${name}:${port}/`);
17998
+ if (options.json) {
17999
+ console.log(JSON.stringify({
18000
+ cert: TLS_CERT_FILE,
18001
+ key: TLS_KEY_FILE,
18002
+ names,
18003
+ authority: existsSync(rootCa) ? rootCa : null,
18004
+ open,
18005
+ urls
18006
+ }, null, 2));
18007
+ return;
18008
+ }
18009
+ console.log(`${chalk.green("✓")} ${TLS_CERT_FILE} for ${names.join(", ")} — .env serves it (TLS_CERT, TLS_KEY).`);
18010
+ if (!trusted) console.log(` This machine’s browsers trust it once mkcert’s authority is installed here: run ${chalk.bold("mkcert -install")} at a terminal (it may ask for your password).`);
18011
+ console.log("");
18012
+ if (!open) console.log(`Open it to your network: ${chalk.bold("HOST=0.0.0.0")} in .env — anyone on that network can then reach it.`);
18013
+ console.log(`Start it, and on the phone open ${urls.map((url) => chalk.bold(url)).join(" or ")}`);
18014
+ console.log("");
18015
+ console.log(`The phone has to trust the authority that signed it: ${chalk.bold(rootCa)} — that file only, never rootCA-key.pem beside it, which signs for anything.`);
18016
+ console.log(" iPhone, iPad: AirDrop or mail it, install the profile (Settings › General › VPN & Device Management), then turn on full trust in Settings › General › About › Certificate Trust Settings.");
18017
+ console.log(" Android: Settings › Security › Encryption & credentials › Install a certificate › CA certificate.");
18018
+ console.log(`A certificate names the addresses it was made for: when the router gives this machine another, run plitzi cert again — or open ${t$1(hostname())}, which stays.`);
18019
+ };
17707
18020
  //#endregion
17708
18021
  //#region src/commands/check.ts
17709
18022
  /** The account `verify` signs in as for the pages that send a visitor to sign in: the project's, in `.env`. */
@@ -17971,8 +18284,8 @@ var momentChanges = (before, after, state) => {
17971
18284
  const appeared = after.shown.filter((id) => !before.shown.includes(id));
17972
18285
  const gone = before.shown.filter((id) => !after.shown.includes(id));
17973
18286
  const listed = (ids) => ids.length > 6 ? `${ids.slice(0, 6).join(", ")} and ${String(ids.length - 6)} more` : ids.join(", ");
17974
- const stateBefore = e$5(state.before) ? state.before : {};
17975
- const stateAfter = e$5(state.after) ? state.after : {};
18287
+ const stateBefore = e$6(state.before) ? state.before : {};
18288
+ const stateAfter = e$6(state.after) ? state.after : {};
17976
18289
  const said = (value) => value === void 0 ? "unset" : JSON.stringify(value);
17977
18290
  const stateChanges = [.../* @__PURE__ */ new Set([...Object.keys(stateBefore), ...Object.keys(stateAfter)])].flatMap((key) => said(stateBefore[key]) === said(stateAfter[key]) ? [] : [`state.${key}: ${said(stateBefore[key])} → ${said(stateAfter[key])}`]);
17978
18291
  return [
@@ -18300,7 +18613,7 @@ var heldText = (report, asked) => {
18300
18613
  /** A value's shape in a few words: a list's length, an object's keys, a short value as it is. */
18301
18614
  var shapeOf = (value) => {
18302
18615
  if (Array.isArray(value)) return `a list of ${String(value.length)}`;
18303
- if (e$5(value)) {
18616
+ if (e$6(value)) {
18304
18617
  const keys = Object.keys(value);
18305
18618
  return `{ ${keys.slice(0, 8).join(", ")}${keys.length > 8 ? ", …" : ""} }`;
18306
18619
  }
@@ -18454,7 +18767,7 @@ var readFunctionsFiles = (root) => s$2(path.join(root, h$1), {
18454
18767
  var readFunctionsState = async (root) => {
18455
18768
  try {
18456
18769
  const value = JSON.parse(await fs.readFile(path.join(root, FUNCTIONS_STATE_FILE), "utf8"));
18457
- if (!e$5(value) || typeof value.space !== "number" || typeof value.version !== "string" || !e$5(value.files)) return;
18770
+ if (!e$6(value) || typeof value.space !== "number" || typeof value.version !== "string" || !e$6(value.files)) return;
18458
18771
  const files = Object.fromEntries(Object.entries(value.files).flatMap(([name, text]) => typeof text === "string" ? [[name, text]] : []));
18459
18772
  return {
18460
18773
  space: value.space,
@@ -18619,7 +18932,7 @@ var printRun = (task, report) => {
18619
18932
  var paramsOf = (given) => {
18620
18933
  try {
18621
18934
  const params = given ? JSON.parse(given) : {};
18622
- if (e$5(params)) return params;
18935
+ if (e$6(params)) return params;
18623
18936
  } catch {}
18624
18937
  fail("--params is a JSON object of the task’s params, by name: --params '{\"name\":\"Ada\"}'");
18625
18938
  };
@@ -18647,7 +18960,7 @@ var tryFunction = async (task, options) => {
18647
18960
  }
18648
18961
  printRun(task, reply.data);
18649
18962
  };
18650
- var isLocalRunner = (value) => e$5(value) && typeof value.createLocalFunctions === "function";
18963
+ var isLocalRunner = (value) => e$6(value) && typeof value.createLocalFunctions === "function";
18651
18964
  /**
18652
18965
  * This project's own runner: `dev` runs the functions with the `@plitzi/sdk-server` the project installed, so the build,
18653
18966
  * the checks, the isolates and the limits are the platform's own, at the version the project is on.
@@ -18722,7 +19035,7 @@ var devFunction = async (task, options) => {
18722
19035
  * to change it — and the files of it the project serves itself, fetched once.
18723
19036
  */
18724
19037
  /** What the platform answered is an export: of the format this CLI writes, with every part it reads. */
18725
- var isExport = (value) => e$5(value) && value.format === 1 && e$5(value.space) && e$5(value.version) && e$5(value.source) && e$5(value.builtOnly) && e$5(value.report) && e$5(value.functions) && e$5(value.data) && Array.isArray(value.actions) && Array.isArray(value.assets) && Array.isArray(value.visitorRoles);
19038
+ var isExport = (value) => e$6(value) && value.format === 1 && e$6(value.space) && e$6(value.version) && e$6(value.source) && e$6(value.builtOnly) && e$6(value.report) && e$6(value.functions) && e$6(value.data) && Array.isArray(value.actions) && Array.isArray(value.assets) && Array.isArray(value.visitorRoles);
18726
19039
  /** How a version is said: `the draft`, `production (latest)`, `production r3`. */
18727
19040
  var versionLabel = ({ environment, revision }) => {
18728
19041
  if (environment === "main") return "the draft";
@@ -18745,7 +19058,7 @@ var requestExport = async (connection, from, { source, version, name = from }) =
18745
19058
  ok: true,
18746
19059
  value: data
18747
19060
  };
18748
- const said = e$5(data) && typeof data.error === "string" ? data.error : void 0;
19061
+ const said = e$6(data) && typeof data.error === "string" ? data.error : void 0;
18749
19062
  return {
18750
19063
  ok: false,
18751
19064
  error: {
@@ -19246,8 +19559,8 @@ var merge = (shape, value) => {
19246
19559
  for (const item of value) merge(shape.array.item, item);
19247
19560
  return;
19248
19561
  }
19249
- const keys = e$5(value) ? Object.keys(value) : [];
19250
- if (e$5(value) && keys.length >= 2 && keys.every((key) => !KEY.test(key))) {
19562
+ const keys = e$6(value) ? Object.keys(value) : [];
19563
+ if (e$6(value) && keys.length >= 2 && keys.every((key) => !KEY.test(key))) {
19251
19564
  shape.map ??= {
19252
19565
  keys: [],
19253
19566
  value: emptyShape()
@@ -19256,7 +19569,7 @@ var merge = (shape, value) => {
19256
19569
  for (const field of Object.values(value)) merge(shape.map.value, field);
19257
19570
  return;
19258
19571
  }
19259
- if (e$5(value)) {
19572
+ if (e$6(value)) {
19260
19573
  shape.object ??= {
19261
19574
  count: 0,
19262
19575
  fields: /* @__PURE__ */ new Map()
@@ -19320,14 +19633,14 @@ var rowsOf = (value) => {
19320
19633
  const candidates = [];
19321
19634
  const visit = (node, at) => {
19322
19635
  if (Array.isArray(node)) {
19323
- if (node.some(e$5)) candidates.push({
19636
+ if (node.some(e$6)) candidates.push({
19324
19637
  path: at,
19325
19638
  rows: node
19326
19639
  });
19327
19640
  node.forEach((item, index) => visit(item, `${at}[${String(index)}]`));
19328
19641
  return;
19329
19642
  }
19330
- if (e$5(node)) for (const [key, field] of Object.entries(node)) visit(field, at ? `${at}.${key}` : key);
19643
+ if (e$6(node)) for (const [key, field] of Object.entries(node)) visit(field, at ? `${at}.${key}` : key);
19331
19644
  };
19332
19645
  visit(value, "");
19333
19646
  return candidates.sort((a, b) => b.rows.length - a.rows.length)[0];
@@ -19336,14 +19649,14 @@ var rowsOf = (value) => {
19336
19649
  var shorten = (value) => {
19337
19650
  if (typeof value === "string") return value.length > 80 ? `${value.slice(0, 77)}…` : value;
19338
19651
  if (Array.isArray(value)) return value.length > 3 ? [...value.slice(0, 3).map(shorten), `… ${String(value.length - 3)} more`] : value.map(shorten);
19339
- if (e$5(value)) return Object.fromEntries(Object.entries(value).map(([key, field]) => [key, shorten(field)]));
19652
+ if (e$6(value)) return Object.fromEntries(Object.entries(value).map(([key, field]) => [key, shorten(field)]));
19340
19653
  return value;
19341
19654
  };
19342
19655
  var describeData = (value) => {
19343
19656
  const shape = emptyShape();
19344
19657
  merge(shape, value);
19345
19658
  const rows = rowsOf(value);
19346
- const first = rows?.rows.find(e$5);
19659
+ const first = rows?.rows.find(e$6);
19347
19660
  return {
19348
19661
  shape: render(shape, 0),
19349
19662
  ...rows && first ? { example: {
@@ -19357,7 +19670,7 @@ var dataDescribe = async (file, options) => {
19357
19670
  try {
19358
19671
  value = JSON.parse(await fs.readFile(file, "utf-8"));
19359
19672
  } catch (error) {
19360
- const missing = e$5(error) && error.code === "ENOENT";
19673
+ const missing = e$6(error) && error.code === "ENOENT";
19361
19674
  const beside = missing ? await fs.readdir(path.dirname(file)).then((names) => names.filter((name) => name.endsWith(".json")), () => []) : [];
19362
19675
  const nearest = closest(path.basename(file), beside);
19363
19676
  fail(missing ? `${file} does not exist${nearest ? ` — did you mean ${path.join(path.dirname(file), nearest)}?` : beside.length > 0 ? `: the JSON there is ${beside.join(", ")}` : "."}` : `${file} is not a JSON file this can read: ${error instanceof Error ? error.message : String(error)}`);
@@ -19861,7 +20174,7 @@ var V = (e) => {
19861
20174
  for (let n of e.filter((e) => x$1.test(e))) t.set(M(n), [...t.get(M(n)) ?? [], n]);
19862
20175
  return [...t].flatMap(([e, t]) => t.slice(1).map((n) => y("plugin-type-taken", `${o$5}/${n}/`, `${o$5}/${t[0]}/ and ${o$5}/${n}/ are both the plugin ${e}: the server registers a folder by its name with a small first letter, and keeps one.`, `Put them in one folder, or rename one: git mv ${o$5}/${n} ${o$5}/<AnotherName>`)));
19863
20176
  };
19864
- var H = (t) => e$5(t.assets) ? Object.values(t.assets).flatMap((t) => e$5(t) && typeof t.src == "string" && typeof t.type == "string" ? [{
20177
+ var H = (t) => e$6(t.assets) ? Object.values(t.assets).flatMap((t) => e$6(t) && typeof t.src == "string" && typeof t.type == "string" ? [{
19865
20178
  src: t.src,
19866
20179
  type: t.type,
19867
20180
  isMain: t.isMain === !0
@@ -19875,7 +20188,7 @@ var U = (t, n) => {
19875
20188
  } catch (e) {
19876
20189
  return { problem: y("vendor-manifest-invalid", i, `${i} is not JSON (${e instanceof Error ? e.message : String(e)}), so ${n} does not run.`, a) };
19877
20190
  }
19878
- if (!e$5(o)) return { problem: y("vendor-manifest-invalid", i, `${i} is not a manifest, so ${n} does not run.`, a) };
20191
+ if (!e$6(o)) return { problem: y("vendor-manifest-invalid", i, `${i} is not a manifest, so ${n} does not run.`, a) };
19879
20192
  let s = H(o), l = s.find((e) => e.type === "script" && e.isMain) ?? s.find((e) => e.type === "script");
19880
20193
  if (!l) return { problem: y("vendor-script-missing", i, `${i} names no script to run, so ${n} does not run.`, a) };
19881
20194
  if (!C(t, path.posix.join(r, l.src), "file")) return { problem: y("vendor-script-missing", i, `${i} names ${l.src} as the script to run, and ${r}/${l.src} is not there, so ${n} does not run.`, a) };
@@ -19999,7 +20312,7 @@ var ce = (e, t) => {
19999
20312
  var Z = (t) => {
20000
20313
  try {
20001
20314
  let n = JSON.parse(readFileSync(path.join(t, "package.json"), "utf-8"));
20002
- return e$5(n) && [n.dependencies, n.devDependencies].some((t) => e$5(t) && "@plitzi/sdk-server" in t);
20315
+ return e$6(n) && [n.dependencies, n.devDependencies].some((t) => e$6(t) && "@plitzi/sdk-server" in t);
20003
20316
  } catch {
20004
20317
  return !1;
20005
20318
  }
@@ -20054,7 +20367,7 @@ var declarationsFile = (root) => {
20054
20367
  const manifest = path.join(dir, "node_modules", PACKAGE, "package.json");
20055
20368
  if (existsSync(manifest)) {
20056
20369
  const parsed = JSON.parse(readFileSync(manifest, "utf-8"));
20057
- const types = e$5(parsed) && typeof parsed.types === "string" ? parsed.types : "dist/index.d.ts";
20370
+ const types = e$6(parsed) && typeof parsed.types === "string" ? parsed.types : "dist/index.d.ts";
20058
20371
  return path.join(path.dirname(manifest), types);
20059
20372
  }
20060
20373
  if (path.dirname(dir) === dir) return;
@@ -20353,9 +20666,9 @@ var editSources = async (root, fixes) => {
20353
20666
  /** A plan as `plitzi space fix --json` prints it. */
20354
20667
  var planFromJson = (stdout) => {
20355
20668
  const parsed = JSON.parse(stdout);
20356
- if (!e$5(parsed)) return { problem: "the space did not answer as a plan" };
20669
+ if (!e$6(parsed)) return { problem: "the space did not answer as a plan" };
20357
20670
  if (typeof parsed.problem === "string") return { problem: parsed.problem };
20358
- const entries = (value) => Array.isArray(value) ? value.filter(e$5) : [];
20671
+ const entries = (value) => Array.isArray(value) ? value.filter(e$6) : [];
20359
20672
  const elementOf = (entry) => typeof entry.elementId === "string" ? entry.elementId : null;
20360
20673
  return {
20361
20674
  fixes: entries(parsed.fixes).map((entry) => ({
@@ -20384,7 +20697,7 @@ var planAfresh = async () => {
20384
20697
  });
20385
20698
  return planFromJson(stdout);
20386
20699
  } catch (error) {
20387
- const printed = e$5(error) && typeof error.stdout === "string" ? error.stdout.trim() : "";
20700
+ const printed = e$6(error) && typeof error.stdout === "string" ? error.stdout.trim() : "";
20388
20701
  if (printed.startsWith("{")) return planFromJson(printed);
20389
20702
  return { problem: error instanceof Error ? error.message.split("\n").slice(0, 3).join(" ") : String(error) };
20390
20703
  }
@@ -20468,7 +20781,7 @@ var vendorFileOf = (name) => `${name.replace(/^@/, "").replace("/", "__")}.js`;
20468
20781
  var manifestOf$1 = async (entry, name) => {
20469
20782
  for (let dir = path.dirname(entry); dir !== path.dirname(dir); dir = path.dirname(dir)) try {
20470
20783
  const parsed = JSON.parse(await fs.readFile(path.join(dir, "package.json"), "utf8"));
20471
- if (e$5(parsed) && parsed.name === name) return {
20784
+ if (e$6(parsed) && parsed.name === name) return {
20472
20785
  version: typeof parsed.version === "string" ? parsed.version : "?",
20473
20786
  license: typeof parsed.license === "string" ? parsed.license : "no licence stated"
20474
20787
  };
@@ -20507,7 +20820,7 @@ var bundle = async (root, name, withDefault) => {
20507
20820
  exports
20508
20821
  };
20509
20822
  } catch (error) {
20510
- return { errors: e$5(error) && Array.isArray(error.errors) ? error.errors.flatMap((entry) => e$5(entry) && typeof entry.text === "string" ? [{ text: entry.text }] : []) : [{ text: error instanceof Error ? error.message : String(error) }] };
20823
+ return { errors: e$6(error) && Array.isArray(error.errors) ? error.errors.flatMap((entry) => e$6(entry) && typeof entry.text === "string" ? [{ text: entry.text }] : []) : [{ text: error instanceof Error ? error.message : String(error) }] };
20511
20824
  }
20512
20825
  };
20513
20826
  /** Why esbuild could not bundle it, in the words that say what to do: a Node module is a package that cannot run here. */
@@ -20605,8 +20918,8 @@ var siteOwnership = async (url, options) => {
20605
20918
  problem: answered.error
20606
20919
  };
20607
20920
  const { status, data } = answered.value.reply;
20608
- const space = e$5(data) && e$5(data.space) && typeof data.space.name === "string" ? data.space.name : "";
20609
- if (status === 200 && e$5(data) && typeof data.domain === "string") return {
20921
+ const space = e$6(data) && e$6(data.space) && typeof data.space.name === "string" ? data.space.name : "";
20922
+ if (status === 200 && e$6(data) && typeof data.domain === "string") return {
20610
20923
  ok: true,
20611
20924
  said: `${data.domain}, verified for ${space || "one of your spaces"}`
20612
20925
  };
@@ -20818,10 +21131,10 @@ var s$1 = (e) => a.find(({ pattern: t }) => t.test(e))?.what;
20818
21131
  var c$1 = (e) => typeof e == "string" && r.some((t) => t === e);
20819
21132
  var l$1 = (e) => typeof e == "string" && /^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(e);
20820
21133
  var u = (e) => Array.isArray(e) && e.length > 0 && e.every((e) => typeof e == "string");
20821
- var d = (t) => e$5(t) && Object.values(t).every((e) => typeof e == "string");
21134
+ var d = (t) => e$6(t) && Object.values(t).every((e) => typeof e == "string");
20822
21135
  var f = (e) => Math.floor(e.length * 3 / 4) - (e.endsWith("==") ? 2 : +!!e.endsWith("="));
20823
21136
  var p = (t, i) => {
20824
- if (!e$5(t) || t.format !== 1) return {
21137
+ if (!e$6(t) || t.format !== 1) return {
20825
21138
  ok: !1,
20826
21139
  problems: ["Not a source snapshot of format 1"]
20827
21140
  };
@@ -21039,17 +21352,17 @@ var PackError = class extends Error {};
21039
21352
  /** The declaration, or what is missing from it — named, so the fix is one edit away. */
21040
21353
  var checkDeclaration = (value, where) => {
21041
21354
  const missing = (field) => new PackError(`${where}: the declaration has no ${field}. Write it the way \`plitzi plugin add\` does.`);
21042
- if (!e$5(value) || typeof value.type !== "string" || !value.type) throw missing("`type`");
21355
+ if (!e$6(value) || typeof value.type !== "string" || !value.type) throw missing("`type`");
21043
21356
  const { content } = value;
21044
- if (!e$5(content)) throw missing("`content`");
21357
+ if (!e$6(content)) throw missing("`content`");
21045
21358
  for (const field of [
21046
21359
  "attributes",
21047
21360
  "definition",
21048
21361
  "builder",
21049
21362
  "market",
21050
21363
  "defaultStyle"
21051
- ]) if (!e$5(content[field])) throw missing(`\`content.${field}\``);
21052
- if (!e$5(content.definition) || typeof content.definition.label !== "string") throw missing("`content.definition.label`");
21364
+ ]) if (!e$6(content[field])) throw missing(`\`content.${field}\``);
21365
+ if (!e$6(content.definition) || typeof content.definition.label !== "string") throw missing("`content.definition.label`");
21053
21366
  return value;
21054
21367
  };
21055
21368
  var toImportPath = (file) => file.split(path.sep).join("/");
@@ -21460,7 +21773,7 @@ var verdictOf = ({ given, was, here }) => {
21460
21773
  * one still at the range the space last asked for follows the space, and one the project changed stays as it is.
21461
21774
  */
21462
21775
  var mergeDependencies = (manifest, asked, before) => {
21463
- const current = e$5(manifest.dependencies) ? manifest.dependencies : {};
21776
+ const current = e$6(manifest.dependencies) ? manifest.dependencies : {};
21464
21777
  const dependencies = { ...current };
21465
21778
  const changed = [];
21466
21779
  const kept = [];
@@ -21545,7 +21858,7 @@ var pull = async (options) => {
21545
21858
  if (options.dryRun) {
21546
21859
  const packageText = await fs.readFile(path.join(root, "package.json"), "utf-8");
21547
21860
  const manifest = JSON.parse(packageText);
21548
- const { changed } = mergeDependencies(e$5(manifest) ? manifest : {}, next.dependencies, origin.dependencies);
21861
+ const { changed } = mergeDependencies(e$6(manifest) ? manifest : {}, next.dependencies, origin.dependencies);
21549
21862
  sayDryRun(`plitzi space pull — ${origin.space.name}, ${versionLabel(asked.version)}`, [
21550
21863
  ...await filesWouldWrite(root, of("write")),
21551
21864
  ...of("remove").map((file) => `- ${file}`),
@@ -21569,7 +21882,7 @@ var pull = async (options) => {
21569
21882
  await Promise.all(removed.map((file) => fs.rm(path.join(root, file), { force: true })));
21570
21883
  const packageFile = path.join(root, "package.json");
21571
21884
  const manifest = JSON.parse(await fs.readFile(packageFile, "utf-8"));
21572
- const dependencies = mergeDependencies(e$5(manifest) ? manifest : {}, next.dependencies, origin.dependencies);
21885
+ const dependencies = mergeDependencies(e$6(manifest) ? manifest : {}, next.dependencies, origin.dependencies);
21573
21886
  if (dependencies.changed.length > 0) await fs.writeFile(packageFile, `${JSON.stringify(dependencies.manifest, null, 2)}\n`);
21574
21887
  const dataKept = of("keep").some((file) => file.startsWith(`${m}/`));
21575
21888
  const files = Object.fromEntries([...[...given].map(([file, bytes]) => [file, digest(bytes)]), ...[...untouched].flatMap((file) => origin.files[file] ? [[file, origin.files[file]]] : [])]);
@@ -21855,9 +22168,9 @@ var projectActions = async (root) => {
21855
22168
  return;
21856
22169
  }
21857
22170
  const loaded = await import(pathToFileURL(file).href);
21858
- const listed = e$5(loaded) ? loaded.actions : void 0;
22171
+ const listed = e$6(loaded) ? loaded.actions : void 0;
21859
22172
  if (!Array.isArray(listed)) return { problem: `${ACTIONS_ENTRY} exports no \`actions\` list: it is where the project’s actions are.` };
21860
- return listed.flatMap((entry) => e$5(entry) && typeof entry.id === "string" && e$5(entry.document) ? [{
22173
+ return listed.flatMap((entry) => e$6(entry) && typeof entry.id === "string" && e$6(entry.document) ? [{
21861
22174
  identifier: entry.id,
21862
22175
  name: typeof entry.document.name === "string" ? entry.document.name : entry.id,
21863
22176
  document: entry.document
@@ -21870,8 +22183,8 @@ var projectActions = async (root) => {
21870
22183
  var projectConnectors = async (root) => {
21871
22184
  const read = await readJsonFolder(path.join(root, "src/connectors"));
21872
22185
  if (!read || read.length === 0) return;
21873
- const broken = read.find(({ value }) => !e$5(value) || typeof value.id !== "string" || !e$5(value.manifest));
21874
- return broken ? { problem: `src/connectors/${broken.file} is not a connector: { "id", "name", "manifest" }.` } : read.flatMap(({ value }) => e$5(value) && typeof value.id === "string" && e$5(value.manifest) ? [{
22186
+ const broken = read.find(({ value }) => !e$6(value) || typeof value.id !== "string" || !e$6(value.manifest));
22187
+ return broken ? { problem: `src/connectors/${broken.file} is not a connector: { "id", "name", "manifest" }.` } : read.flatMap(({ value }) => e$6(value) && typeof value.id === "string" && e$6(value.manifest) ? [{
21875
22188
  identifier: value.id,
21876
22189
  name: typeof value.name === "string" ? value.name : value.id,
21877
22190
  manifest: value.manifest
@@ -21996,7 +22309,7 @@ var keepSource = async (connection, spaceId, bytes) => {
21996
22309
  };
21997
22310
  //#endregion
21998
22311
  //#region src/commands/runtime.ts
21999
- var isPacker = (value) => e$5(value) && typeof value.packRuntime === "function";
22312
+ var isPacker = (value) => e$6(value) && typeof value.packRuntime === "function";
22000
22313
  /** This project's own packer — read, since it is the project's. */
22001
22314
  var projectPacker = async (root) => {
22002
22315
  try {
@@ -23615,6 +23928,10 @@ var stepsOf = (scripts) => [
23615
23928
  name: "lint",
23616
23929
  command: scripts.lint
23617
23930
  }] : [],
23931
+ ...scripts.test ? [{
23932
+ name: "test",
23933
+ command: scripts.test
23934
+ }] : [],
23618
23935
  ...Object.hasOwn(scripts, "format") && scripts.format.startsWith("prettier") ? [{
23619
23936
  name: "format",
23620
23937
  command: "prettier --check .",
@@ -23649,7 +23966,7 @@ var run = (root, command) => new Promise((resolve) => {
23649
23966
  var tail = (output) => output.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim() !== "").slice(-15);
23650
23967
  var scriptsOf = async (root) => {
23651
23968
  const parsed = JSON.parse(await fs.readFile(path.join(root, "package.json"), "utf-8"));
23652
- const scripts = e$5(parsed) && e$5(parsed.scripts) ? parsed.scripts : {};
23969
+ const scripts = e$6(parsed) && e$6(parsed.scripts) ? parsed.scripts : {};
23653
23970
  return Object.fromEntries(Object.entries(scripts).filter((entry) => typeof entry[1] === "string"));
23654
23971
  };
23655
23972
  /** A page the browser was sent away from — one for signed-in visitors, sent to sign in. */
@@ -23774,17 +24091,24 @@ var projectModule = async (root, specifier) => {
23774
24091
  * file of the project's, installs, or touches git is recommended instead.
23775
24092
  */
23776
24093
  var readText = (file) => fs.readFile(file, "utf-8").catch(() => void 0);
24094
+ /**
24095
+ * A file of the project's written as its own Prettier writes it: a repair that left the next `format --check` — the one
24096
+ * `verify` runs — something to say would trade one problem for another.
24097
+ */
24098
+ var writeFormatted = async (root, file, text) => {
24099
+ const format = await projectFormatter(root);
24100
+ await fs.writeFile(path.join(root, file), await format(file, text));
24101
+ };
23777
24102
  /** `package.json` changed by `edit`, written with the indentation it has — so the diff is the lines that changed. */
23778
24103
  var editManifest = (root, says, edit) => ({
23779
24104
  says,
23780
24105
  run: async () => {
23781
- const file = path.join(root, "package.json");
23782
- const text = await readText(file) ?? "{}";
24106
+ const text = await readText(path.join(root, "package.json")) ?? "{}";
23783
24107
  const parsed = JSON.parse(text);
23784
- if (!e$5(parsed)) throw new Error("package.json is not an object");
24108
+ if (!e$6(parsed)) throw new Error("package.json is not an object");
23785
24109
  edit(parsed);
23786
24110
  const indent = /^\{\n([ \t]+)"/.exec(text)?.[1] ?? " ";
23787
- await fs.writeFile(file, `${JSON.stringify(parsed, null, indent)}\n`);
24111
+ await writeFormatted(root, "package.json", `${JSON.stringify(parsed, null, indent)}\n`);
23788
24112
  }
23789
24113
  });
23790
24114
  /** Lines added to the end of `.gitignore`, one each. */
@@ -23818,10 +24142,7 @@ var freshSecret = (root, name) => ({
23818
24142
  says: `writes a new ${name} to .env`,
23819
24143
  run: async () => {
23820
24144
  const file = path.join(root, ".env");
23821
- const text = await readText(file) ?? "";
23822
- const line = `${name}=${randomBytes(32).toString("hex")}`;
23823
- const pattern = new RegExp(`^${name}=.*$`, "m");
23824
- await fs.writeFile(file, pattern.test(text) ? text.replace(pattern, line) : `${text}${text === "" || text.endsWith("\n") ? "" : "\n"}${line}\n`);
24145
+ await fs.writeFile(file, withSetting(await readText(file) ?? "", name, randomBytes(32).toString("hex")));
23825
24146
  }
23826
24147
  });
23827
24148
  /**
@@ -23836,14 +24157,14 @@ var editTsConfig = async (root, file, says, edit) => {
23836
24157
  } catch {
23837
24158
  return;
23838
24159
  }
23839
- if (!e$5(parsed)) return;
24160
+ if (!e$6(parsed)) return;
23840
24161
  const config = parsed;
23841
24162
  return {
23842
24163
  says,
23843
24164
  run: async () => {
23844
24165
  edit(config);
23845
24166
  const indent = /^\{\n([ \t]+)"/.exec(text ?? "")?.[1] ?? " ";
23846
- await fs.writeFile(path.join(root, file), `${JSON.stringify(config, null, indent)}\n`);
24167
+ await writeFormatted(root, file, `${JSON.stringify(config, null, indent)}\n`);
23847
24168
  }
23848
24169
  };
23849
24170
  };
@@ -23930,11 +24251,11 @@ var readTsConfig = async (root, file) => {
23930
24251
  } catch (error) {
23931
24252
  return { problem: error instanceof Error ? error.message : String(error) };
23932
24253
  }
23933
- if (!e$5(parsed)) return { problem: "it is not an object" };
24254
+ if (!e$6(parsed)) return { problem: "it is not an object" };
23934
24255
  return {
23935
24256
  ...typeof parsed.extends === "string" ? { extends: parsed.extends } : {},
23936
24257
  include: Array.isArray(parsed.include) ? parsed.include.filter((entry) => typeof entry === "string") : [],
23937
- options: e$5(parsed.compilerOptions) ? parsed.compilerOptions : {}
24258
+ options: e$6(parsed.compilerOptions) ? parsed.compilerOptions : {}
23938
24259
  };
23939
24260
  };
23940
24261
  /** A repair that may be none, spread into a finding's details. */
@@ -23960,9 +24281,32 @@ var NODE_OPTIONS = [
23960
24281
  why: "an `enum` or a parameter property passes the typecheck and fails at start"
23961
24282
  }
23962
24283
  ];
24284
+ /** The options `TSCONFIG_BASE` sets, as the CLI writes it for this project. */
24285
+ var baseOptions = (answers) => {
24286
+ const base = JSON.parse(tsconfigBase(answers));
24287
+ return e$6(base) && e$6(base.compilerOptions) ? base.compilerOptions : {};
24288
+ };
24289
+ /**
24290
+ * A `tsconfig.json` from before the CLI kept its options apart, made to extend them: what it sets the same as the base
24291
+ * goes, and only what is the project's own is left in it.
24292
+ */
24293
+ var extendBase = (root, answers) => editTsConfig(root, "tsconfig.json", `makes tsconfig.json extend ${TSCONFIG_BASE}, keeping the options it changes`, (json) => {
24294
+ const base = baseOptions(answers);
24295
+ const own = e$6(json.compilerOptions) ? Object.entries(json.compilerOptions).filter(([key, value]) => JSON.stringify(value) !== JSON.stringify(base[key])) : [];
24296
+ json.extends = `./${TSCONFIG_BASE}`;
24297
+ if (own.length > 0) json.compilerOptions = Object.fromEntries(own);
24298
+ else Reflect.deleteProperty(json, "compilerOptions");
24299
+ });
23963
24300
  var tsconfigChecks = async ({ root, answers }) => {
23964
24301
  const config = await readTsConfig(root, "tsconfig.json");
23965
- if (!config) return [];
24302
+ if (!config) return [say$8.error("tsconfig-missing", "There is no tsconfig.json: nothing type-checks the project, and the lint reads none of it.", {
24303
+ file: "tsconfig.json",
24304
+ fix: `Write one that extends ./${TSCONFIG_BASE} and includes the project's folders.`,
24305
+ repair: {
24306
+ says: "writes tsconfig.json",
24307
+ run: () => writeFormatted(root, "tsconfig.json", tsconfig$1(answers))
24308
+ }
24309
+ })];
23966
24310
  if ("problem" in config) return [say$8.error("tsconfig-unreadable", `tsconfig.json cannot be read: ${config.problem}.`, {
23967
24311
  file: "tsconfig.json",
23968
24312
  fix: "plitzi upgrade files shows the CLI’s"
@@ -23983,6 +24327,11 @@ var tsconfigChecks = async ({ root, answers }) => {
23983
24327
  fix: `Add "${n$7}" to "include".`,
23984
24328
  ...await repairOf$1(included(n$7))
23985
24329
  }));
24330
+ if (!config.extends) findings.push(say$8.warning("tsconfig-stands-alone", `tsconfig.json does not extend ./${TSCONFIG_BASE}: the compiler options the CLI keeps up never reach it.`, {
24331
+ file: "tsconfig.json",
24332
+ fix: `Set "extends": "./${TSCONFIG_BASE}" and keep in "compilerOptions" only what is the project's own.`,
24333
+ ...await repairOf$1(extendBase(root, answers))
24334
+ }));
23986
24335
  if (!config.extends) {
23987
24336
  for (const { option, why } of NODE_OPTIONS) if (config.options[option] !== true) findings.push(say$8.warning("tsconfig-node-option", `tsconfig.json does not set ${option}: ${why}.`, {
23988
24337
  file: "tsconfig.json",
@@ -24000,7 +24349,7 @@ var buildChecks = async (root) => {
24000
24349
  fix: "plitzi upgrade files shows the CLI’s"
24001
24350
  })];
24002
24351
  const manifest = JSON.parse(await readOptional(path.join(root, "package.json")) ?? "{}");
24003
- const scripts = e$5(manifest) && e$5(manifest.scripts) ? manifest.scripts : {};
24352
+ const scripts = e$6(manifest) && e$6(manifest.scripts) ? manifest.scripts : {};
24004
24353
  const startProd = typeof scripts["start:prod"] === "string" ? scripts["start:prod"] : "";
24005
24354
  const builds = typeof scripts.build === "string" && scripts.build.includes("tsconfig.build.json");
24006
24355
  const runs = /node\s+(?:--\S+\s+)*(\S+\.js)\b/.exec(startProd)?.[1];
@@ -24086,7 +24435,7 @@ var gitChecks = async ({ root, answers }) => {
24086
24435
  /** The minimum the project's own server holds a signing secret to — or nothing, when it cannot be asked. */
24087
24436
  var minimumSecret = async (root) => {
24088
24437
  const actions = await projectModule(root, "@plitzi/sdk-server/actions").catch(() => void 0);
24089
- const minimum = e$5(actions) ? actions.MIN_SIGNING_SECRET_LENGTH : void 0;
24438
+ const minimum = e$6(actions) ? actions.MIN_SIGNING_SECRET_LENGTH : void 0;
24090
24439
  return typeof minimum === "number" ? minimum : void 0;
24091
24440
  };
24092
24441
  var envChecks = async ({ root, answers }) => {
@@ -24127,8 +24476,21 @@ var envChecks = async ({ root, answers }) => {
24127
24476
  }));
24128
24477
  return findings;
24129
24478
  };
24479
+ /** The project's notes, which `CLAUDE.md` imports and `AGENTS.md` sends every agent to: one a project from before them lacks. */
24480
+ var notesChecks = async ({ root, answers }) => {
24481
+ if (await readOptional(path.join(root, "NOTES.md")) !== void 0) return [];
24482
+ return [say$8.warning("notes-missing", `There is no ${PROJECT_NOTES}: the agents are sent to it for what this project has to know, and find nothing.`, {
24483
+ file: PROJECT_NOTES,
24484
+ fix: `Write ${PROJECT_NOTES}: how the project is built, what must not be undone.`,
24485
+ repair: {
24486
+ says: `writes ${PROJECT_NOTES}`,
24487
+ run: () => writeFormatted(root, PROJECT_NOTES, projectNotes(answers))
24488
+ }
24489
+ })];
24490
+ };
24130
24491
  var checkConfig = async (context) => [
24131
24492
  ...await tsconfigChecks(context),
24493
+ ...await notesChecks(context),
24132
24494
  ...await gitignoreChecks(context),
24133
24495
  ...await gitChecks(context),
24134
24496
  ...await envChecks(context)
@@ -24159,11 +24521,11 @@ var checkData = async ({ root, answers }) => [...answers.mode === "server" ? awa
24159
24521
  * definition, imports of its own files and the contract alone, and the most a bundle may weigh.
24160
24522
  */
24161
24523
  var say$6 = sayer("functions");
24162
- var isProblem = (value) => e$5(value) && typeof value.message === "string";
24524
+ var isProblem = (value) => e$6(value) && typeof value.message === "string";
24163
24525
  /** `buildFunctions` of the project's own sdk-server — or nothing, when it has none that offers it. */
24164
24526
  var projectBuild = async (root) => {
24165
24527
  const runner = await projectModule(root, "@plitzi/sdk-server/functions-runner").catch(() => void 0);
24166
- const build = e$5(runner) ? runner.buildFunctions : void 0;
24528
+ const build = e$6(runner) ? runner.buildFunctions : void 0;
24167
24529
  return typeof build === "function" ? build : void 0;
24168
24530
  };
24169
24531
  var checkFunctions = async ({ root, answers }) => {
@@ -24182,7 +24544,7 @@ var checkFunctions = async ({ root, answers }) => {
24182
24544
  await build(files);
24183
24545
  return [];
24184
24546
  } catch (error) {
24185
- const problems = e$5(error) && Array.isArray(error.problems) ? error.problems.filter(isProblem) : [];
24547
+ const problems = e$6(error) && Array.isArray(error.problems) ? error.problems.filter(isProblem) : [];
24186
24548
  if (problems.length === 0) return [say$6.error("functions-do-not-build", error instanceof Error ? error.message : String(error), { file: h$1 })];
24187
24549
  return problems.map((problem) => say$6.error("functions-do-not-build", problem.message, { file: problem.file ? `${h$1}/${problem.file}${problem.line ? `:${String(problem.line)}` : ""}` : h$1 }));
24188
24550
  }
@@ -24290,7 +24652,7 @@ var moveFiles = async (root, moves) => {
24290
24652
  const manifestFile = path.join(root, "package.json");
24291
24653
  const manifestText = await fs.readFile(manifestFile, "utf-8");
24292
24654
  const manifest = JSON.parse(manifestText);
24293
- if (e$5(manifest) && e$5(manifest.scripts)) {
24655
+ if (e$6(manifest) && e$6(manifest.scripts)) {
24294
24656
  const scripts = Object.fromEntries(Object.entries(manifest.scripts).map(([name, command]) => {
24295
24657
  if (typeof command !== "string") return [name, command];
24296
24658
  const next = scriptRewritten(command, moves);
@@ -24302,7 +24664,7 @@ var moveFiles = async (root, moves) => {
24302
24664
  return [name, next];
24303
24665
  }));
24304
24666
  const indent = /^\{\n([ \t]+)"/.exec(manifestText)?.[1] ?? " ";
24305
- await fs.writeFile(manifestFile, `${JSON.stringify({
24667
+ await writeFormatted(root, "package.json", `${JSON.stringify({
24306
24668
  ...manifest,
24307
24669
  scripts
24308
24670
  }, null, indent)}\n`);
@@ -24543,7 +24905,7 @@ var say$4 = sayer("machinery");
24543
24905
  var ESSENTIAL = /* @__PURE__ */ new Set([
24544
24906
  MAIN_FILE,
24545
24907
  r$7,
24546
- "tsconfig.json",
24908
+ TSCONFIG_BASE,
24547
24909
  "tsconfig.build.json",
24548
24910
  "vite.config.ts",
24549
24911
  "index.html",
@@ -24604,11 +24966,11 @@ var LOCKFILES = {
24604
24966
  "yarn.lock": "yarn",
24605
24967
  "pnpm-lock.yaml": "pnpm"
24606
24968
  };
24607
- var stringsOf = (value) => e$5(value) ? Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string")) : {};
24969
+ var stringsOf = (value) => e$6(value) ? Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string")) : {};
24608
24970
  var readManifest = async (dir) => {
24609
24971
  try {
24610
24972
  const parsed = JSON.parse(await fs.readFile(path.join(dir, "package.json"), "utf-8"));
24611
- return e$5(parsed) ? parsed : void 0;
24973
+ return e$6(parsed) ? parsed : void 0;
24612
24974
  } catch {
24613
24975
  return;
24614
24976
  }
@@ -24650,12 +25012,12 @@ var manifestChecks = (context) => {
24650
25012
  manifest.type = "module";
24651
25013
  })
24652
25014
  }));
24653
- if (typeof (e$5(context.manifest.engines) ? context.manifest.engines.node : void 0) !== "string") findings.push(say$3.warning("engines-missing", `package.json names no Node version: the project needs Node ${NODE_ENGINES.node}.`, {
25015
+ if (typeof (e$6(context.manifest.engines) ? context.manifest.engines.node : void 0) !== "string") findings.push(say$3.warning("engines-missing", `package.json names no Node version: the project needs Node ${NODE_ENGINES.node}.`, {
24654
25016
  file: "package.json",
24655
25017
  fix: `Add "engines": { "node": "${NODE_ENGINES.node}" } to package.json.`,
24656
25018
  repair: editManifest(context.root, `sets "engines": { "node": "${NODE_ENGINES.node}" } in package.json`, (manifest) => {
24657
25019
  manifest.engines = {
24658
- ...e$5(manifest.engines) ? manifest.engines : {},
25020
+ ...e$6(manifest.engines) ? manifest.engines : {},
24659
25021
  ...NODE_ENGINES
24660
25022
  };
24661
25023
  })
@@ -24853,7 +25215,7 @@ var lockfileChecks = async (context) => {
24853
25215
  };
24854
25216
  var checkPackages = async (context) => {
24855
25217
  const scaffold = JSON.parse(packageJson$1(context.answers));
24856
- const ours = e$5(scaffold) ? {
25218
+ const ours = e$6(scaffold) ? {
24857
25219
  ...stringsOf(scaffold.dependencies),
24858
25220
  ...stringsOf(scaffold.devDependencies)
24859
25221
  } : {};
@@ -24907,10 +25269,10 @@ var assetsLeftOut = {
24907
25269
  });
24908
25270
  }
24909
25271
  };
24910
- var isMessage = (value) => e$5(value) && typeof value.text === "string";
25272
+ var isMessage = (value) => e$6(value) && typeof value.text === "string";
24911
25273
  /** What a failed build says: each of its messages — or, failing in a way that names none, its error, never nothing. */
24912
25274
  var failureOf = (error) => {
24913
- const messages = e$5(error) && Array.isArray(error.errors) ? error.errors.filter(isMessage) : [];
25275
+ const messages = e$6(error) && Array.isArray(error.errors) ? error.errors.filter(isMessage) : [];
24914
25276
  return messages.length > 0 ? messages : [{
24915
25277
  text: error instanceof Error ? error.message : String(error),
24916
25278
  location: null
@@ -24920,8 +25282,8 @@ var failureOf = (error) => {
24920
25282
  var declaredType = async (file) => {
24921
25283
  try {
24922
25284
  const loaded = await import(pathToFileURL(file).href);
24923
- const declaration = e$5(loaded) ? loaded.default : void 0;
24924
- return e$5(declaration) && typeof declaration.type === "string" ? { type: declaration.type } : { problem: "exports no declaration by default: `export default { type: …, … }`" };
25285
+ const declaration = e$6(loaded) ? loaded.default : void 0;
25286
+ return e$6(declaration) && typeof declaration.type === "string" ? { type: declaration.type } : { problem: "exports no declaration by default: `export default { type: …, … }`" };
24925
25287
  } catch (error) {
24926
25288
  return { problem: `does not load: ${error instanceof Error ? error.message : String(error)}` };
24927
25289
  }
@@ -25019,7 +25381,7 @@ var checkFolder = async (root, folder, packages) => {
25019
25381
  * one shadowed by a built copy and the built ones' manifests are the layout check's (`plitzi doctor`'s `layout`).
25020
25382
  */
25021
25383
  var checkPlugins = async ({ root, manifest }) => {
25022
- const declared = /* @__PURE__ */ new Set([...e$5(manifest.dependencies) ? Object.keys(manifest.dependencies) : [], ...e$5(manifest.devDependencies) ? Object.keys(manifest.devDependencies) : []]);
25384
+ const declared = /* @__PURE__ */ new Set([...e$6(manifest.dependencies) ? Object.keys(manifest.dependencies) : [], ...e$6(manifest.devDependencies) ? Object.keys(manifest.devDependencies) : []]);
25023
25385
  const folders = await foldersOf(path.join(root, o$5));
25024
25386
  return (await Promise.all(folders.map((folder) => checkFolder(root, folder, declared)))).flat();
25025
25387
  };
@@ -25076,7 +25438,7 @@ var packageOf = (specifier) => {
25076
25438
  const parts = specifier.split("/");
25077
25439
  return specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
25078
25440
  };
25079
- var stringKeys = (value) => e$5(value) ? Object.keys(value) : [];
25441
+ var stringKeys = (value) => e$6(value) ? Object.keys(value) : [];
25080
25442
  /** How `from` imports `to`: relative, with its extension, as Node wants it. */
25081
25443
  var specifierOf = (from, to) => {
25082
25444
  const written = path.relative(path.dirname(from), to).split(path.sep).join("/");
@@ -25419,7 +25781,7 @@ var projectContext = async (project) => {
25419
25781
  return { problem: `package.json is not JSON: ${error instanceof Error ? error.message : String(error)}` };
25420
25782
  }
25421
25783
  const plitzi = project.plitzi?.kind === "project" ? project.plitzi : void 0;
25422
- if (!plitzi || !e$5(manifest)) return { problem: "plitzi doctor checks a project plitzi create wrote — one that depends on @plitzi/plitzi-sdk and starts at src/main.ts — and this is not one." };
25784
+ if (!plitzi || !e$6(manifest)) return { problem: "plitzi doctor checks a project plitzi create wrote — one that depends on @plitzi/plitzi-sdk and starts at src/main.ts — and this is not one." };
25423
25785
  const root = await fs.realpath(project.root);
25424
25786
  const record = await readScaffoldRecord(root);
25425
25787
  const manager = await lockfileManager([root]) ?? record?.packageManager ?? project.packageManager ?? "npm";
@@ -25553,7 +25915,7 @@ var SCOPE = "@plitzi";
25553
25915
  var versionOf = async (folder) => {
25554
25916
  try {
25555
25917
  const manifest = JSON.parse(await fs.readFile(path.join(folder, "package.json"), "utf-8"));
25556
- return e$5(manifest) && typeof manifest.name === "string" && typeof manifest.version === "string" ? [manifest.name, manifest.version] : void 0;
25918
+ return e$6(manifest) && typeof manifest.name === "string" && typeof manifest.version === "string" ? [manifest.name, manifest.version] : void 0;
25557
25919
  } catch {
25558
25920
  return;
25559
25921
  }
@@ -25573,7 +25935,7 @@ var installedPackages = async (root) => {
25573
25935
  var nameOf = async (root) => {
25574
25936
  try {
25575
25937
  const manifest = JSON.parse(await fs.readFile(path.join(root, "package.json"), "utf-8"));
25576
- return e$5(manifest) && typeof manifest.name === "string" ? manifest.name : path.basename(root);
25938
+ return e$6(manifest) && typeof manifest.name === "string" ? manifest.name : path.basename(root);
25577
25939
  } catch {
25578
25940
  return path.basename(root);
25579
25941
  }
@@ -26963,7 +27325,7 @@ pageGroup.command("import").argument("<url>", "The page to start from: https://e
26963
27325
  390
26964
27326
  ])).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));
26965
27327
  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));
26966
- 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));
27328
+ program.command("verify").description("Whether the project is left passing: author, lints, types, its tests, 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));
26967
27329
  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));
26968
27330
  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));
26969
27331
  elementGroup.command("remove").argument("<id>", "The element, by its id: ent-trust").description("Take an element out of the code that writes it, with what only it used, then check the space").option("--json", "One object, for a tool or an agent").action((id, options) => remove$1(id, options));
@@ -26972,6 +27334,7 @@ elementGroup.command("readings", { hidden: true }).action(() => elements());
26972
27334
  program.command("explain").argument("[name]", "An element, a step, a trigger, a problem’s code, a transformer or a helper: container, navigate, class-and-css, bindTemplate, motion").description("What a name means when authoring a space — what it takes, fires, reads and how it is written").option("--list <kind>", "Every name of a kind: elements, steps, triggers, codes, transformers, helpers").option("--json", "One object, for a tool or an agent").action((name, options) => explainCommand(name, options));
26973
27335
  program.command("upgrade").argument("[parts...]", `Only these: ${UPGRADE_PARTS.join(", ")} — every one when none is named`).description("Bring the project up to this CLI: its machinery files, package.json, the skills and renamed names — shown; with --write made").option("--write", "Make the changes: files nobody changed replaced, package.json merged and installed, names renamed").option("--take <files...>", "Replace these files of your own too (`all` for every one), after reading their diff").option("--no-install", "Write package.json and leave the install to you").option("--json", "One object, for a tool or an agent").action((parts, options) => upgrade(parts, options));
26974
27336
  program.command("doctor").description("Whether the project the CLI set up is whole — packages, its files, configs, what Node runs, plugins, data files, functions, records — each problem with its fix. Not the space: that is `npm run author`, and how it is written `plitzi space lint`").option("--fix", "Repair what is simple and safe — a layout an older CLI left, dead files, .gitignore, a missing secret — then check again").option(...DRY_RUN_OPTION).option("--strict", "Warnings fail too (exit 1): for a CI that keeps the project up to its CLI").option("--json", "One object, for a tool or an agent").action((options) => doctor(options));
27337
+ program.command("cert").description("HTTPS while developing, for a phone or a tablet on your Wi-Fi: a certificate for this machine and its network addresses (mkcert), named in .env").option("--name <names...>", "Other names this machine answers by, in the certificate too: studio.tailnet.ts.net").option("--json", "One object, for a tool or an agent").action((options) => cert(options));
26975
27338
  program.command("feedback").description("Start a report to Plitzi: writes the page in tmp/feedback/ with the versions and the project already in it, and tells the agent how to fill and publish it — to send as a link").option("--previous <url>", "An earlier report this one continues — its numbering, and what changed. Repeat it", collect, []).option("--json", "One object: { file, facts, brief }, for a tool or an agent").action((options) => feedback(options));
26976
27339
  spaceGroup.command("lint").description("How the space in src/space/ is written, eslint’s way: each practice to change at its file and line, what to write instead, and what authoring suggests. Not whether it authors — `npm run author` — nor the project: `plitzi doctor`").option("--strict", "Warnings fail too (exit 1)").addOption(new Option("--max-warnings <n>", "More warnings than this fail (exit 1)").argParser(count)).option("--json", "One object: { scope, notChecked, findings }, for a tool or an agent").action((options) => lint(options));
26977
27340
  program.command("skills").description("The Plitzi skills an agent reads in .claude/skills/").command("update").description("Bring them up to the Plitzi packages this project has installed, each replaced whole (`upgrade skills --write`)").option(...DRY_RUN_OPTION).action((options) => upgrade(["skills"], { write: !options.dryRun }));